diff --git a/phpstan.neon b/phpstan.neon index 033fbeffbb2d22589d5fa389a28300014d79d784..d19b518b1d183d40054c47b1506592a6c4a1c1ae 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -103,7 +103,7 @@ parameters: # @see https://www.drupal.org/project/feeds/issues/3461141 - message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#" - count: 3 + count: 4 path: src/Plugin/Type/Target/FieldTargetBase.php # HttpFetcherResult and RawFetcherResult have an optional parameter diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5516b7e4201d41840eae4592f957e3832f8f4a7c..b13c6d02c1f4bd4f4869b3975ee5a572991be64b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,17 +16,17 @@ <listener class="\Drupal\Tests\Listeners\DrupalListener"> </listener> </listeners> - <!-- Filter for coverage reports. --> - <filter> - <whitelist> + <!-- Settings for coverage reports. --> + <coverage> + <include> <directory>./src</directory> - <!-- By definition test classes have no tests. --> - <exclude> - <directory suffix="Test.php">./</directory> - <directory suffix="TestBase.php">./</directory> - </exclude> - </whitelist> - </filter> + </include> + <!-- By definition test classes have no tests. --> + <exclude> + <directory suffix="Test.php">./</directory> + <directory suffix="TestBase.php">./</directory> + </exclude> + </coverage> <logging> <log type="coverage-html" target="coverage" showUncoveredFiles="true"/> </logging> diff --git a/src/Feeds/Target/ConfigEntityReference.php b/src/Feeds/Target/ConfigEntityReference.php index a7cddf2ea083c61899c64f99cc010b258897cf61..ac9f0e0681cd45de82a1ed10dcfc7baadd5284dc 100644 --- a/src/Feeds/Target/ConfigEntityReference.php +++ b/src/Feeds/Target/ConfigEntityReference.php @@ -8,6 +8,7 @@ use Drupal\Core\Config\TypedConfigManagerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -77,13 +78,16 @@ class ConfigEntityReference extends FieldTargetBase implements ConfigurableTarge * The transliteration manager. * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager * The manager for managing config schema type plugins. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFinderInterface $entity_finder, TransliterationInterface $transliteration, TypedConfigManagerInterface $typed_config_manager) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFinderInterface $entity_finder, TransliterationInterface $transliteration, TypedConfigManagerInterface $typed_config_manager, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { $this->entityTypeManager = $entity_type_manager; $this->entityFinder = $entity_finder; $this->transliteration = $transliteration; $this->typedConfigManager = $typed_config_manager; - parent::__construct($configuration, $plugin_id, $plugin_definition); + parent::__construct($configuration, $plugin_id, $plugin_definition, $field_type_plugin_manager); } /** @@ -97,7 +101,8 @@ class ConfigEntityReference extends FieldTargetBase implements ConfigurableTarge $container->get('entity_type.manager'), $container->get('feeds.entity_finder'), $container->get('transliteration'), - $container->get('config.typed') + $container->get('config.typed'), + $container->get('plugin.manager.field.field_type'), ); } diff --git a/src/Feeds/Target/DateTargetBase.php b/src/Feeds/Target/DateTargetBase.php index 7500cf7a8ed3ec5f0b7d4c90d6b7729b78d35a48..d5edd617b3b032b27a6826ec7ddb606c461b8886 100644 --- a/src/Feeds/Target/DateTargetBase.php +++ b/src/Feeds/Target/DateTargetBase.php @@ -5,6 +5,7 @@ namespace Drupal\feeds\Feeds\Target; use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Datetime\TimeZoneFormHelper; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\feeds\Plugin\Type\Target\ConfigurableTargetInterface; @@ -34,9 +35,12 @@ abstract class DateTargetBase extends FieldTargetBase implements ConfigurableTar * The plugin definition. * @param \Drupal\Core\Config\ImmutableConfig $system_date_config * The system date configuration. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImmutableConfig $system_date_config) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImmutableConfig $system_date_config, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $field_type_plugin_manager); $this->systemDateConfig = $system_date_config; } @@ -49,6 +53,7 @@ abstract class DateTargetBase extends FieldTargetBase implements ConfigurableTar $plugin_id, $plugin_definition, $container->get('config.factory')->get('system.date'), + $container->get('plugin.manager.field.field_type'), ); } diff --git a/src/Feeds/Target/DateTime.php b/src/Feeds/Target/DateTime.php index b9a7bda608a9d0b8b9c1f11c71a374afee974ac5..2ccd907f2b9ea692d350f56c62145a45444f0ed1 100644 --- a/src/Feeds/Target/DateTime.php +++ b/src/Feeds/Target/DateTime.php @@ -3,6 +3,7 @@ namespace Drupal\feeds\Feeds\Target; use Drupal\Core\Config\ImmutableConfig; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; /** @@ -25,8 +26,8 @@ class DateTime extends DateTargetBase { /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImmutableConfig $system_date_config) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $system_date_config); + public function __construct(array $configuration, $plugin_id, array $plugin_definition, ImmutableConfig $system_date_config, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $system_date_config, $field_type_plugin_manager); $this->storageFormat = $this->settings['datetime_type'] === 'date' ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT; } diff --git a/src/Feeds/Target/EntityReference.php b/src/Feeds/Target/EntityReference.php index 4705a34cce18450a70cc90289ac3784235b93abf..66fbc0fccd49776e809d5e28e1b825bc5ce017f4 100644 --- a/src/Feeds/Target/EntityReference.php +++ b/src/Feeds/Target/EntityReference.php @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\TypedData\DataDefinitionInterface; @@ -69,13 +70,16 @@ class EntityReference extends FieldTargetBase implements ConfigurableTargetInter * The entity field manager. * @param \Drupal\feeds\EntityFinderInterface $entity_finder * The Feeds entity finder service. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityFinderInterface $entity_finder) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, EntityFinderInterface $entity_finder, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { $this->entityTypeManager = $entity_type_manager; $this->entityFieldManager = $entity_field_manager; $this->entityFinder = $entity_finder; - parent::__construct($configuration, $plugin_id, $plugin_definition); + parent::__construct($configuration, $plugin_id, $plugin_definition, $field_type_plugin_manager); } /** @@ -89,6 +93,7 @@ class EntityReference extends FieldTargetBase implements ConfigurableTargetInter $container->get('entity_type.manager'), $container->get('entity_field.manager'), $container->get('feeds.entity_finder'), + $container->get('plugin.manager.field.field_type'), ); } diff --git a/src/Feeds/Target/File.php b/src/Feeds/Target/File.php index b86ad3e47d58185379eabc12bfc84b5442d629ed..f5c248a4928442fa44a7c31f744ed14eb4b4a8f2 100644 --- a/src/Feeds/Target/File.php +++ b/src/Feeds/Target/File.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\File\Exception\FileException; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormStateInterface; @@ -97,11 +98,14 @@ class File extends EntityReference { * The file repository. * @param \Drupal\Core\Config\ImmutableConfig $file_config * The system.file configuration. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ClientInterface $client, Token $token, EntityFieldManagerInterface $entity_field_manager, EntityFinderInterface $entity_finder, FileSystemInterface $file_system, FileRepositoryInterface $file_repository, ImmutableConfig $file_config) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ClientInterface $client, Token $token, EntityFieldManagerInterface $entity_field_manager, EntityFinderInterface $entity_finder, FileSystemInterface $file_system, FileRepositoryInterface $file_repository, ImmutableConfig $file_config, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { $this->client = $client; $this->token = $token; - parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $entity_finder); + parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $entity_finder, $field_type_plugin_manager); $this->fileExtensions = array_filter(explode(' ', $this->settings['file_extensions'])); $this->fileSystem = $file_system; $this->fileRepository = $file_repository; @@ -124,6 +128,7 @@ class File extends EntityReference { $container->get('file_system'), $container->get('file.repository'), $container->get('config.factory')->get('system.file'), + $container->get('plugin.manager.field.field_type'), ); } diff --git a/src/Feeds/Target/Integer.php b/src/Feeds/Target/Integer.php index 4c70c22633a12c8131233cf1810618742b8ef37c..25cb6632c0086a8ae89e018e20c25ae602a7495f 100644 --- a/src/Feeds/Target/Integer.php +++ b/src/Feeds/Target/Integer.php @@ -34,6 +34,7 @@ class Integer extends Number { */ protected function prepareValue($delta, array &$values) { $value = is_string($values['value']) ? trim($values['value']) : $values['value']; + $value = $this->convertLabelToKey($value); $values['value'] = is_numeric($value) ? (int) $value : ''; } diff --git a/src/Feeds/Target/Number.php b/src/Feeds/Target/Number.php index 4b1e9c3d178878cf92041112bd66504c0ccc8249..5c8a32c8d53dafcabd96963a5c6a5c4bfef3f4ab 100644 --- a/src/Feeds/Target/Number.php +++ b/src/Feeds/Target/Number.php @@ -43,6 +43,7 @@ class Number extends FieldTargetBase { */ protected function prepareValue($delta, array &$values) { $values['value'] = is_string($values['value']) ? trim($values['value']) : $values['value']; + $values['value'] = $this->convertLabelToKey($values['value']); if (!is_numeric($values['value'])) { $values['value'] = ''; diff --git a/src/Feeds/Target/Password.php b/src/Feeds/Target/Password.php index d37d137bf71b5c756d04617c689a93df9cec508e..48ac9ef74795240ed6bd7fb37811a74d5476c39b 100644 --- a/src/Feeds/Target/Password.php +++ b/src/Feeds/Target/Password.php @@ -4,6 +4,7 @@ namespace Drupal\feeds\Feeds\Target; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Password\PasswordInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -65,9 +66,12 @@ class Password extends FieldTargetBase implements ConfigurableTargetInterface, C * The password hash service. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, PasswordInterface $password_hasher, ModuleHandlerInterface $module_handler) { - parent::__construct($configuration, $plugin_id, $plugin_definition); + public function __construct(array $configuration, $plugin_id, array $plugin_definition, PasswordInterface $password_hasher, ModuleHandlerInterface $module_handler, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { + parent::__construct($configuration, $plugin_id, $plugin_definition, $field_type_plugin_manager); $this->passwordHasher = $password_hasher; $this->moduleHandler = $module_handler; } @@ -87,7 +91,8 @@ class Password extends FieldTargetBase implements ConfigurableTargetInterface, C $plugin_id, $plugin_definition, $password_hasher, - $container->get('module_handler') + $container->get('module_handler'), + $container->get('plugin.manager.field.field_type'), ); } diff --git a/src/Feeds/Target/Text.php b/src/Feeds/Target/Text.php index 8285abb13eeb5c07aab5c99ad58fa676618dbde6..38914e5615edfabb0dbc00a319fbf48de9b94319 100644 --- a/src/Feeds/Target/Text.php +++ b/src/Feeds/Target/Text.php @@ -4,6 +4,7 @@ namespace Drupal\feeds\Feeds\Target; use Drupal\Core\Config\Entity\ConfigEntityStorageInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; @@ -52,15 +53,18 @@ class Text extends StringTarget implements ConfigurableTargetInterface, Containe * The current user. * @param \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $filter_format_storage * The storage for filter_format config entities. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition, AccountInterface $user, ?ConfigEntityStorageInterface $filter_format_storage = NULL) { + public function __construct(array $configuration, $plugin_id, array $plugin_definition, AccountInterface $user, ?ConfigEntityStorageInterface $filter_format_storage = NULL, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { $this->user = $user; if (!isset($filter_format_storage)) { @trigger_error('Calling ' . __METHOD__ . '() without the $filter_format_storage argument is deprecated in feeds:3.0.0-rc2 and will be required in feeds:4.0.0. See https://www.drupal.org/node/3473603', E_USER_DEPRECATED); $filter_format_storage = \Drupal::service('entity_type.manager')->getStorage('filter_format'); } $this->filterFormatStorage = $filter_format_storage; - parent::__construct($configuration, $plugin_id, $plugin_definition); + parent::__construct($configuration, $plugin_id, $plugin_definition, $field_type_plugin_manager); } /** diff --git a/src/Plugin/Type/Target/FieldTargetBase.php b/src/Plugin/Type/Target/FieldTargetBase.php index 9df61ac8238b15f01c8e05f35d2f3297d1c642aa..72f735b436e0a78579f401f4956094003490b89f 100644 --- a/src/Plugin/Type/Target/FieldTargetBase.php +++ b/src/Plugin/Type/Target/FieldTargetBase.php @@ -5,6 +5,7 @@ namespace Drupal\feeds\Plugin\Type\Target; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\TranslatableInterface; use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -14,9 +15,11 @@ use Drupal\feeds\FeedInterface; use Drupal\feeds\FeedTypeInterface; use Drupal\feeds\FieldTargetDefinition; use Drupal\feeds\Plugin\Type\Processor\EntityProcessorInterface; +use Drupal\options\Plugin\Field\FieldType\ListItemBase; +use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Helper class for field mappers. + * Base class for field mappers. */ abstract class FieldTargetBase extends TargetBase implements ConfigurableTargetInterface, TranslatableTargetInterface { @@ -34,6 +37,51 @@ abstract class FieldTargetBase extends TargetBase implements ConfigurableTargetI */ protected $languageManager; + /** + * The field type plugin manager. + * + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface + */ + protected $fieldTypePluginManager; + + /** + * Constructs a FieldTargetBase instance. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldTypePluginManagerInterface|null $field_type_plugin_manager + * (optional) The field type plugin manager. Passing this argument will be + * required in feeds:4.0.0; omitting it is deprecated since feeds:3.1.0. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, ?FieldTypePluginManagerInterface $field_type_plugin_manager = NULL) { + $this->targetDefinition = $configuration['target_definition']; + $this->settings = $this->targetDefinition->getFieldDefinition()->getSettings(); + + if (!isset($field_type_plugin_manager)) { + @trigger_error('Calling ' . __METHOD__ . '() without the $field_type_plugin_manager argument is deprecated in feeds:3.1.0 and will be required in feeds:4.0.0. See https://www.drupal.org/node/3473603', E_USER_DEPRECATED); + $field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type'); + } + $this->fieldTypePluginManager = $field_type_plugin_manager; + + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('plugin.manager.field.field_type') + ); + } + /** * {@inheritdoc} */ @@ -76,15 +124,6 @@ abstract class FieldTargetBase extends TargetBase implements ConfigurableTargetI ->addProperty('value'); } - /** - * {@inheritdoc} - */ - public function __construct(array $configuration, $plugin_id, array $plugin_definition) { - $this->targetDefinition = $configuration['target_definition']; - $this->settings = $this->targetDefinition->getFieldDefinition()->getSettings(); - parent::__construct($configuration, $plugin_id, $plugin_definition); - } - /** * {@inheritdoc} */ @@ -178,10 +217,43 @@ abstract class FieldTargetBase extends TargetBase implements ConfigurableTargetI */ protected function prepareValue($delta, array &$values) { foreach ($values as $column => $value) { - $values[$column] = (string) $value; + if ($column == 'value') { + $values[$column] = $this->convertLabelToKey($value); + } + else { + $values[$column] = (string) $value; + } } } + /** + * For fields with allowed values, allow the source to specify a label. + * + * @param mixed $value + * The value to look up. + * + * @return string + * The converted value. + */ + protected function convertLabelToKey($value): string { + $allowed_values = $this->getAllowedValues(); + if (count($allowed_values) > 0 && is_scalar($value)) { + if (array_key_exists($value, $allowed_values)) { + // It's already a key, allow it as-is. + return (string) $value; + } + else { + // Try to match label to key. + $key = array_search($value, $allowed_values, TRUE); + if ($key !== FALSE) { + return (string) $key; + } + } + } + + return (string) $value; + } + /** * Constructs a base query which is used to find an existing entity. * @@ -434,4 +506,36 @@ abstract class FieldTargetBase extends TargetBase implements ConfigurableTargetI return $this->getLanguageManager()->getDefaultLanguage()->getId(); } + /** + * Determines whether the field is a list field. + * + * @return bool + * True if the field is a list field, false otherwise. + */ + protected function isListField(): bool { + $field_definition = $this->targetDefinition->getFieldDefinition(); + $field_type_class = $this->fieldTypePluginManager->getDefinition($field_definition->getType())['class'] ?? NULL; + + return is_subclass_of($field_type_class, ListItemBase::class); + } + + /** + * Get the list of allowed values for this field. + * + * @return array + * A list of allowed values in an associative array. + */ + protected function getAllowedValues(): array { + $allowed_values = []; + + if ($this->isListField()) { + $allowed_values = $this->targetDefinition->getFieldDefinition()->getSetting('allowed_values'); + if (is_callable($allowed_values)) { + $allowed_values = call_user_func($allowed_values); + } + } + + return $allowed_values; + } + } diff --git a/tests/src/Kernel/Feeds/Target/ListTest.php b/tests/src/Kernel/Feeds/Target/ListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..994218fe2bda538b4304479fa4fce311925c1ba5 --- /dev/null +++ b/tests/src/Kernel/Feeds/Target/ListTest.php @@ -0,0 +1,189 @@ +<?php + +namespace Drupal\Tests\feeds\Kernel\Feeds\Target; + +use Drupal\node\NodeInterface; +use Drupal\node\Entity\Node; +use Drupal\Tests\feeds\Kernel\FeedsKernelTestBase; + +/** + * Tests importing values into list fields using Feeds. + * + * @group feeds + */ +class ListTest extends FeedsKernelTestBase { + + /** + * Provides list field types with their allowed values. + * + * @return array + * Each item contains: + * - field type: 'list_string', 'list_integer', or 'list_float'. + * - allowed values: associative array of allowed keys => labels. + */ + public static function listFieldTypeProvider(): array { + return [ + 'List (string)' => [ + 'field_type' => 'list_string', + 'allowed_values' => ['apple' => 'Apple', 'banana' => 'Banana'], + ], + 'List (integer)' => [ + 'field_type' => 'list_integer', + 'allowed_values' => [1 => 'One', 2 => 'Two'], + ], + 'List (float)' => [ + 'field_type' => 'list_float', + 'allowed_values' => ['1.1' => 'Low', '2.2' => 'High'], + ], + ]; + } + + /** + * Tests importing list values using keys as CSV input. + * + * @dataProvider listFieldTypeProvider + */ + public function testImportListByKey(string $field_type, array $allowed_values): void { + $field_name = 'field_' . $field_type; + $expected_key = (string) array_key_first($allowed_values); + + // Create the list field. + $this->createFieldWithStorage($field_name, [ + 'type' => $field_type, + 'storage' => [ + 'settings' => [ + 'allowed_values' => $allowed_values, + ], + ], + ]); + + // Create a feed type. + $feed_type = $this->createFeedTypeForCsv([ + 'key_column' => 'Key', + ], [ + 'mappings' => array_merge($this->getDefaultMappings(), [ + [ + 'target' => $field_name, + 'map' => ['value' => 'key_column'], + ], + ]), + ]); + + // Create a CSV file. + $csv_data = "guid,title,key_column\n2,Node with key,$expected_key"; + $file_path = $this->createCsvFile($csv_data); + + // Import a feed. + $feed = $this->createFeed($feed_type->id(), [ + 'source' => $file_path, + ]); + $feed->import(); + + $node = Node::load(1); + $this->assertInstanceof(NodeInterface::class, $node, 'Node was created.'); + $this->assertSame($expected_key, $node->get($field_name)->value); + } + + /** + * Tests importing an invalid list value (label or key). + * + * Feeds should ignore or reject values not in the allowed list. + * + * @dataProvider listFieldTypeProvider + */ + public function testImportInvalidListValue(string $field_type, array $allowed_values): void { + $field_name = 'field_' . $field_type; + + $this->createFieldWithStorage($field_name, [ + 'type' => $field_type, + 'storage' => [ + 'settings' => [ + 'allowed_values' => $allowed_values, + ], + ], + ]); + + $feed_type = $this->createFeedTypeForCsv([ + 'invalid_column' => 'Invalid', + ], [ + 'mappings' => array_merge($this->getDefaultMappings(), [ + [ + 'target' => $field_name, + 'map' => ['value' => 'invalid_column'], + ], + ]), + ]); + + // Create a CSV file. + $csv_data = "guid,title,invalid_column\n3,Node with invalid value,8"; + $file_path = $this->createCsvFile($csv_data); + + // Import a feed. + $feed = $this->createFeed($feed_type->id(), [ + 'source' => $file_path, + ]); + $feed->import(); + + $node = Node::load(1); + $this->assertNull($node, 'Node was not created because of validation errors.'); + + // Check for the expected validation error. + $messages = \Drupal::messenger()->all(); + foreach ($messages['warning'] as $warning) { + $this->assertStringContainsString('The value you selected is not a valid choice.', $warning); + } + + // Clear the logged messages so no failure is reported on tear down. + $this->logger->clearMessages(); + } + + /** + * Tests importing list values using labels as CSV input. + * + * @dataProvider listFieldTypeProvider + */ + public function testImportListByLabel(string $field_type, array $allowed_values): void { + // Use label value from the allowed values. + $import_label = reset($allowed_values); + $expected_key = (string) array_search($import_label, $allowed_values, TRUE); + + $field_name = 'field_' . $field_type; + + // Create the list field. + $this->createFieldWithStorage($field_name, [ + 'type' => $field_type, + 'storage' => [ + 'settings' => [ + 'allowed_values' => $allowed_values, + ], + ], + ]); + + // Create a feed type. + $feed_type = $this->createFeedTypeForCsv([ + 'label_column' => 'Label', + ], [ + 'mappings' => array_merge($this->getDefaultMappings(), [ + [ + 'target' => $field_name, + 'map' => ['value' => 'label_column'], + ], + ]), + ]); + + // Create a CSV file. + $csv_data = "guid,title,label_column\n1,Node with label,$import_label"; + $file_path = $this->createCsvFile($csv_data); + + // Import a feed. + $feed = $this->createFeed($feed_type->id(), [ + 'source' => $file_path, + ]); + $feed->import(); + + $node = Node::load(1); + $this->assertInstanceof(NodeInterface::class, $node, 'Node was created.'); + $this->assertSame($expected_key, $node->get($field_name)->value); + } + +} diff --git a/tests/src/Traits/FeedsCommonTrait.php b/tests/src/Traits/FeedsCommonTrait.php index 0ddbdf5cf29c96602cdb977735fbb20a30a08fa3..ff143ad015dcf6c1f16ece8ea26165c0f4147859 100644 --- a/tests/src/Traits/FeedsCommonTrait.php +++ b/tests/src/Traits/FeedsCommonTrait.php @@ -344,6 +344,25 @@ trait FeedsCommonTrait { } } + /** + * Creates a CSV file on the public file system. + * + * @param string $contents + * The contents of the CSV file. + * + * @return string + * The path to the file that was created. + */ + protected function createCsvFile(string $contents): string { + $directory = 'public://feeds_test'; + $this->container->get('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY); + + $filename = $directory . '/test.csv'; + file_put_contents($filename, $contents); + + return $filename; + } + /** * Prints messages useful for debugging. */ diff --git a/tests/src/Unit/Feeds/Target/ConfigEntityReferenceTest.php b/tests/src/Unit/Feeds/Target/ConfigEntityReferenceTest.php index f2cb86a10a38e6cf26844a8ec5546e027350ba92..d35a53f8086a498d3cd93b803fb0dcaeef4f9328 100644 --- a/tests/src/Unit/Feeds/Target/ConfigEntityReferenceTest.php +++ b/tests/src/Unit/Feeds/Target/ConfigEntityReferenceTest.php @@ -40,7 +40,7 @@ class ConfigEntityReferenceTest extends ConfigEntityReferenceTestBase { 'target_definition' => $this->createTargetDefinitionMock(), 'reference_by' => 'id', ]; - return new ConfigEntityReference($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->entityFinder->reveal(), $this->transliteration->reveal(), $this->typedConfigManager->reveal()); + return new ConfigEntityReference($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->entityFinder->reveal(), $this->transliteration->reveal(), $this->typedConfigManager->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/DateRangeTest.php b/tests/src/Unit/Feeds/Target/DateRangeTest.php index fbdd1df9d674f8bd4c40451ebb59097f1c59117e..680949d89af03e0178e6e3d60e5f984bdb971483 100644 --- a/tests/src/Unit/Feeds/Target/DateRangeTest.php +++ b/tests/src/Unit/Feeds/Target/DateRangeTest.php @@ -59,7 +59,7 @@ class DateRangeTest extends DateTestBase { 'feed_type' => $this->feedType, 'target_definition' => $this->targetDefinition, ]; - return new DateRange($configuration, static::$pluginId, [], $this->systemDateConfig->reveal()); + return new DateRange($configuration, static::$pluginId, [], $this->systemDateConfig->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/DateTimeTest.php b/tests/src/Unit/Feeds/Target/DateTimeTest.php index 028ced3e29883a48a114f2bc19707c8453637dc4..1e5d12c24a0f36291f5e33a68ecfa1328bb9d666 100644 --- a/tests/src/Unit/Feeds/Target/DateTimeTest.php +++ b/tests/src/Unit/Feeds/Target/DateTimeTest.php @@ -59,7 +59,7 @@ class DateTimeTest extends DateTestBase { 'feed_type' => $this->feedType, 'target_definition' => $this->targetDefinition, ]; - return new DateTime($configuration, static::$pluginId, [], $this->systemDateConfig->reveal()); + return new DateTime($configuration, static::$pluginId, [], $this->systemDateConfig->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/EntityReferenceTest.php b/tests/src/Unit/Feeds/Target/EntityReferenceTest.php index 38a3fc04c6a357440bcc9e7f6073d6505d29cfeb..13df924bc3c56a61e1b2f030cd096b9d2545b1a9 100644 --- a/tests/src/Unit/Feeds/Target/EntityReferenceTest.php +++ b/tests/src/Unit/Feeds/Target/EntityReferenceTest.php @@ -52,7 +52,7 @@ class EntityReferenceTest extends EntityReferenceTestBase { 'feed_type' => $this->createMock(FeedTypeInterface::class), 'target_definition' => $this->createTargetDefinitionMock(), ]; - return new EntityReference($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->entityFieldManager->reveal(), $this->entityFinder->reveal()); + return new EntityReference($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->entityFieldManager->reveal(), $this->entityFinder->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/FieldTargetTestBase.php b/tests/src/Unit/Feeds/Target/FieldTargetTestBase.php index 6d6ca098d4e8b470e0b757653e1b626e62852b1a..67d5393383d29c3f829b53753b3ab220ff22f68c 100644 --- a/tests/src/Unit/Feeds/Target/FieldTargetTestBase.php +++ b/tests/src/Unit/Feeds/Target/FieldTargetTestBase.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\feeds\Unit\Feeds\Target; +use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Tests\feeds\Unit\FeedsUnitTestCase; use Drupal\feeds\Exception\EmptyFeedException; use Drupal\feeds\FeedTypeInterface; @@ -20,6 +21,13 @@ abstract class FieldTargetTestBase extends FeedsUnitTestCase { */ protected static $pluginId = ''; + /** + * The field type plugin manager. + * + * @var \Drupal\Core\Field\FieldTypePluginManagerInterface + */ + protected $fieldTypePluginManager; + /** * Returns the target class. * @@ -28,6 +36,15 @@ abstract class FieldTargetTestBase extends FeedsUnitTestCase { */ abstract protected function getTargetClass(); + /** + * {@inheritdoc} + */ + public function setUp(): void { + parent::setUp(); + + $this->fieldTypePluginManager = $this->prophesize(FieldTypePluginManagerInterface::class); + } + /** * Tests the prepareTarget() method. */ @@ -65,7 +82,7 @@ abstract class FieldTargetTestBase extends FeedsUnitTestCase { 'feed_type' => $this->createMock(FeedTypeInterface::class), 'target_definition' => $method($this->getMockFieldDefinition()), ]; - return new $target_class($configuration, static::$pluginId, []); + return new $target_class($configuration, static::$pluginId, [], $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/FileTargetTestBase.php b/tests/src/Unit/Feeds/Target/FileTargetTestBase.php index cd015eb3d93c808ca9ce2e9c8d66171d1414f98c..dc0efe1edb25cedcf3f8e1c5a0b766c4485093f2 100644 --- a/tests/src/Unit/Feeds/Target/FileTargetTestBase.php +++ b/tests/src/Unit/Feeds/Target/FileTargetTestBase.php @@ -114,7 +114,7 @@ abstract class FileTargetTestBase extends FieldTargetTestBase { 'feed_type' => $this->createMock('Drupal\feeds\FeedTypeInterface'), 'target_definition' => $method($field_definition_mock), ]; - return new $target_class($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->client->reveal(), $this->token->reveal(), $this->entityFieldManager->reveal(), $this->entityFinder->reveal(), $this->fileSystem->reveal(), $this->fileRepository->reveal(), $this->fileConfig->reveal()); + return new $target_class($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->client->reveal(), $this->token->reveal(), $this->entityFieldManager->reveal(), $this->entityFinder->reveal(), $this->fileSystem->reveal(), $this->fileRepository->reveal(), $this->fileConfig->reveal(), $this->fieldTypePluginManager->reveal()); } } diff --git a/tests/src/Unit/Feeds/Target/PasswordTest.php b/tests/src/Unit/Feeds/Target/PasswordTest.php index 6d151bb09d7d83c47cc06cdc00478946f8a9a8f2..a919337e71eafc437ed70e86ee1106293cf2119b 100644 --- a/tests/src/Unit/Feeds/Target/PasswordTest.php +++ b/tests/src/Unit/Feeds/Target/PasswordTest.php @@ -63,7 +63,7 @@ class PasswordTest extends FieldTargetTestBase { 'target_definition' => $method($this->getMockFieldDefinition()), ]; - return new Password($configuration, static::$pluginId, [], $this->passwordHasher->reveal(), $this->moduleHandler->reveal()); + return new Password($configuration, static::$pluginId, [], $this->passwordHasher->reveal(), $this->moduleHandler->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/TelephoneTest.php b/tests/src/Unit/Feeds/Target/TelephoneTest.php index b99543f7982663612ebd519cf8751d02e4335054..c76fa90f2db161eeb4f7e4f31c4f19b340abef77 100644 --- a/tests/src/Unit/Feeds/Target/TelephoneTest.php +++ b/tests/src/Unit/Feeds/Target/TelephoneTest.php @@ -38,7 +38,7 @@ class TelephoneTest extends FieldTargetTestBase { 'feed_type' => $this->createMock('Drupal\feeds\FeedTypeInterface'), 'target_definition' => $method($field_definition), ]; - return new Telephone($configuration, static::$pluginId, []); + return parent::instantiatePlugin($configuration); } /** diff --git a/tests/src/Unit/Feeds/Target/TextTest.php b/tests/src/Unit/Feeds/Target/TextTest.php index b5a6313292d4044206ab3c7a17b19833c55ff6f4..f2221b45ee71784ba968ca1199beb31bf9b1c9f8 100644 --- a/tests/src/Unit/Feeds/Target/TextTest.php +++ b/tests/src/Unit/Feeds/Target/TextTest.php @@ -83,6 +83,7 @@ class TextTest extends FieldTargetTestBase { [], $this->createMock(AccountInterface::class), $this->filterFormatStorage->reveal(), + $this->fieldTypePluginManager->reveal(), ]) ->onlyMethods(['getFilterFormats']) ->getMock(); diff --git a/tests/src/Unit/Feeds/Target/TimestampTest.php b/tests/src/Unit/Feeds/Target/TimestampTest.php index 2a64af01dc6c5278465eb798562e6f9100f7f848..8f3440f25e4b4eeb61b4bcc8d7b492bedd858288 100644 --- a/tests/src/Unit/Feeds/Target/TimestampTest.php +++ b/tests/src/Unit/Feeds/Target/TimestampTest.php @@ -37,7 +37,7 @@ class TimestampTest extends DateTestBase { 'feed_type' => $this->createMock(FeedTypeInterface::class), 'target_definition' => $method($this->getMockFieldDefinition()), ]; - return new $target_class($configuration, static::$pluginId, [], $this->systemDateConfig->reveal()); + return new $target_class($configuration, static::$pluginId, [], $this->systemDateConfig->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/UserRoleTest.php b/tests/src/Unit/Feeds/Target/UserRoleTest.php index fc8a1a07eba7350d8fa26fc718952f5423aff2d0..0e7d428826fc409d793ffc2a6f39bb8750f5ca0e 100644 --- a/tests/src/Unit/Feeds/Target/UserRoleTest.php +++ b/tests/src/Unit/Feeds/Target/UserRoleTest.php @@ -79,7 +79,7 @@ class UserRoleTest extends ConfigEntityReferenceTestBase { 'target_definition' => $this->createTargetDefinitionMock(), 'reference_by' => 'label', ]; - return new UserRole($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->entityFinder->reveal(), $this->transliteration->reveal(), $this->typedConfigManager->reveal()); + return new UserRole($configuration, static::$pluginId, [], $this->entityTypeManager->reveal(), $this->entityFinder->reveal(), $this->transliteration->reveal(), $this->typedConfigManager->reveal(), $this->fieldTypePluginManager->reveal()); } /** diff --git a/tests/src/Unit/Feeds/Target/UuidTest.php b/tests/src/Unit/Feeds/Target/UuidTest.php index 5bd0be7c056956ff4d28d6e5ccfef9fa550332fd..f8068f086a9e8807af3fb650b11d15be725c01c5 100644 --- a/tests/src/Unit/Feeds/Target/UuidTest.php +++ b/tests/src/Unit/Feeds/Target/UuidTest.php @@ -48,7 +48,7 @@ class UuidTest extends FieldTargetTestBase { 'feed_type' => $this->createMock(FeedTypeInterface::class), 'target_definition' => $prepareTarget($this->getMockFieldDefinition()), ]; - $target = new Uuid($configuration, 'uuid', []); + $target = $this->instantiatePlugin($configuration); $prepareValue = $this->getProtectedClosure($target, 'prepareValue'); @@ -71,7 +71,7 @@ class UuidTest extends FieldTargetTestBase { 'feed_type' => $this->createMock(FeedTypeInterface::class), 'target_definition' => $prepareTarget($this->getMockFieldDefinition()), ]; - $target = new Uuid($configuration, 'uuid', []); + $target = $this->instantiatePlugin($configuration); $prepareValue = $this->getProtectedClosure($target, 'prepareValue');