diff --git a/README.md b/README.md
index cbe1a78..0abeb5d 100644
--- a/README.md
+++ b/README.md
@@ -63,3 +63,10 @@ For more details and examples see
## Maintainers
Lasha Badashvili - [lashabp](https://www.drupal.org/u/lashabp)
+CSV importer module helps to import content from CSV files.
+
Command-line usage:
+
+
+ - Put your CSV file on your server at /full/path/to/my/csv/file.csv
+ - Run
drush ev "\Drupal::service('plugin.manager.importer')->import('node', 'article', '/full/path/to/my/csv/file.csv', ',', 'node_importer');"
+
diff --git a/src/Form/ImporterForm.php b/src/Form/ImporterForm.php
index eeb0a80..cc14599 100644
--- a/src/Form/ImporterForm.php
+++ b/src/Form/ImporterForm.php
@@ -2,11 +2,11 @@
namespace Drupal\csv_importer\Form;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
-use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
-use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\csv_importer\ParserInterface;
use Drupal\csv_importer\Plugin\ImporterManager;
@@ -271,102 +271,24 @@ class ImporterForm extends FormBase {
return $options;
}
- /**
- * Get entity type fields.
- *
- * @param string $entity_type
- * Entity type.
- * @param string|null $entity_type_bundle
- * Entity type bundle.
- *
- * @return array
- * Entity type fields.
- */
- protected function getEntityTypeFields(string $entity_type, string $entity_type_bundle = NULL) {
- $fields = [];
-
- if (!$entity_type_bundle) {
- $entity_type_bundle = key($this->entityBundleInfo->getBundleInfo($entity_type));
- }
-
- $entity_fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $entity_type_bundle);
- foreach ($entity_fields as $entity_field) {
- $fields['fields'][] = $entity_field->getName();
-
- if ($entity_field->isRequired()) {
- $fields['required'][] = $entity_field->getName();
- }
- }
-
- return $fields;
- }
-
- /**
- * Get entity missing fields.
- *
- * @param string $entity_type
- * Entity type.
- * @param array $required
- * Entity required fields.
- * @param array $csv
- * Parsed CSV.
- *
- * @return array
- * Missing fields.
- */
- protected function getEntityTypeMissingFields(string $entity_type, array $required, array $csv) {
- $entity_definition = $this->entityTypeManager->getDefinition($entity_type);
-
- if ($entity_definition->hasKey('bundle')) {
- unset($required[array_search($entity_definition->getKey('bundle'), $required)]);
- }
-
- $csv_fields = [];
-
- if (!empty($csv)) {
- foreach ($csv[0] as $csv_row) {
- $csv_row = explode('|', $csv_row);
- $csv_fields[] = $csv_row[0];
- }
- }
-
- $csv_fields = array_values(array_unique($csv_fields));
-
- return array_diff($required, $csv_fields);
- }
-
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$entity_type = $form_state->getValue('entity_type');
$entity_type_bundle = NULL;
- $csv = current($form_state->getValue('csv'));
- $csv_parse = $this->parser->getCsvById($csv, $form_state->getUserInput()['delimiter']);
if (isset($form_state->getUserInput()['entity_type_bundle'])) {
$entity_type_bundle = $form_state->getUserInput()['entity_type_bundle'];
}
+ $csv_file_id = current($form_state->getValue('csv'));
+ $delimiter = $form_state->getUserInput()['delimiter'];
+ $plugin_id = $form_state->getUserInput()['plugin_id'];
- $entity_fields = $this->getEntityTypeFields($entity_type, $entity_type_bundle);
-
- if ($required = $this->getEntityTypeMissingFields($entity_type, $entity_fields['required'], $csv_parse)) {
- $render = [
- '#theme' => 'item_list',
- '#items' => $required,
- ];
+ /** @var \Drupal\file\Entity\File $entity */
+ $csv_entity = $this->parser->getCsvEntity($csv_file_id);
- $this->messenger()->addError($this->t('Your CSV has missing required fields: @fields', ['@fields' => $this->renderer->render($render)]));
- }
- else {
- $this->importer->createInstance($form_state->getUserInput()['plugin_id'], [
- 'csv' => $csv_parse,
- 'csv_entity' => $this->parser->getCsvEntity($csv),
- 'entity_type' => $entity_type,
- 'entity_type_bundle' => $entity_type_bundle,
- 'fields' => $entity_fields['fields'],
- ])->process();
- }
+ $this->importer->import($entity_type, $entity_type_bundle, $csv_entity->uri->getString(), $delimiter, $plugin_id, TRUE);
}
}
diff --git a/src/Parser.php b/src/Parser.php
index f242854..36b131a 100644
--- a/src/Parser.php
+++ b/src/Parser.php
@@ -26,38 +26,6 @@ class Parser implements ParserInterface {
$this->entityTypeManager = $entity_type_manager;
}
- /**
- * {@inheritdoc}
- */
- public function getCsvById(int $id, string $delimiter) {
- /** @var \Drupal\file\Entity\File $entity */
- $entity = $this->getCsvEntity($id);
- $return = [];
-
- if (($csv = fopen($entity->uri->getString(), 'r')) !== FALSE) {
- while (($row = fgetcsv($csv, 0, $delimiter)) !== FALSE) {
- $return[] = $row;
- }
-
- fclose($csv);
- }
-
- return $return;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getCsvFieldsById(int $id) {
- $csv = $this->getCsvById($id);
-
- if ($csv && is_array($csv)) {
- return $csv[0];
- }
-
- return NULL;
- }
-
/**
* {@inheritdoc}
*/
diff --git a/src/ParserInterface.php b/src/ParserInterface.php
index 7db97e3..f50478e 100644
--- a/src/ParserInterface.php
+++ b/src/ParserInterface.php
@@ -7,30 +7,6 @@ namespace Drupal\csv_importer;
*/
interface ParserInterface {
- /**
- * Get CSV by id.
- *
- * @param int $id
- * CSV id.
- * @param string $delimiter
- * CSV delimiter.
- *
- * @return array|null
- * Parsed CSV.
- */
- public function getCsvById(int $id, string $delimiter);
-
- /**
- * Get CSV column (first row).
- *
- * @param int $id
- * CSV id.
- *
- * @return array|null
- * CSV field names.
- */
- public function getCsvFieldsById(int $id);
-
/**
* Load CSV.
*
diff --git a/src/Plugin/ImporterBase.php b/src/Plugin/ImporterBase.php
index 37f7ffe..1c3d713 100644
--- a/src/Plugin/ImporterBase.php
+++ b/src/Plugin/ImporterBase.php
@@ -2,15 +2,15 @@
namespace Drupal\csv_importer\Plugin;
-use Drupal\Core\Plugin\PluginBase;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Component\Utility\Unicode;
use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\file\FileRepositoryInterface;
-use Drupal\Component\Utility\Unicode;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -256,4 +256,17 @@ abstract class ImporterBase extends PluginBase implements ImporterInterface {
batch_set($process);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function processNow() {
+ $context = [];
+ if ($data = $this->data()) {
+ foreach ($data['content'] as $content) {
+ $this->add($content, $context);
+ }
+ }
+ $this->finished(TRUE, $context, []);
+ }
+
}
diff --git a/src/Plugin/ImporterInterface.php b/src/Plugin/ImporterInterface.php
index ec0daa0..a207ba8 100644
--- a/src/Plugin/ImporterInterface.php
+++ b/src/Plugin/ImporterInterface.php
@@ -51,4 +51,9 @@ interface ImporterInterface extends PluginInspectionInterface, ContainerFactoryP
*/
public function process();
+ /**
+ * Process without running a batch operation.
+ */
+ public function processNow();
+
}
diff --git a/src/Plugin/ImporterManager.php b/src/Plugin/ImporterManager.php
index 68c0519..d4efef0 100644
--- a/src/Plugin/ImporterManager.php
+++ b/src/Plugin/ImporterManager.php
@@ -4,12 +4,16 @@ namespace Drupal\csv_importer\Plugin;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Provides the Importer plugin manager.
*/
class ImporterManager extends DefaultPluginManager {
+ use MessengerTrait;
+ use StringTranslationTrait;
/**
* Constructs a ImporterManager object.
@@ -29,4 +33,180 @@ class ImporterManager extends DefaultPluginManager {
$this->setCacheBackend($cache_backend, 'importer_info_plugins');
}
+ /**
+ * Mockable wrapper around the entity_type.bundle.info service.
+ */
+ protected function entityBundleInfo() {
+ return \Drupal::service('entity_type.bundle.info');
+ }
+
+ /**
+ * Mockable wrapper around the entity_type.manager service.
+ */
+ protected function entityTypeManager() {
+ return \Drupal::service('entity_type.manager');
+ }
+
+ /**
+ * Mockable wrapper around the entity_field.manager service.
+ */
+ protected function entityFieldManager() {
+ return \Drupal::service('entity_field.manager');
+ }
+
+ /**
+ * Mockable wrapper around the renderer service.
+ */
+ protected function renderer() {
+ return \Drupal::service('renderer');
+ }
+
+ /**
+ * Get entity missing fields.
+ *
+ * @param string $entity_type
+ * Entity type.
+ * @param array $required
+ * Entity required fields.
+ * @param array $csv
+ * Parsed CSV.
+ *
+ * @return array
+ * Missing fields.
+ */
+ protected function getEntityTypeMissingFields(string $entity_type, array $required, array $csv) {
+ $entity_definition = $this->entityTypeManager()->getDefinition($entity_type);
+
+ if ($entity_definition->hasKey('bundle')) {
+ unset($required[array_search($entity_definition->getKey('bundle'), $required)]);
+ }
+
+ $csv_fields = [];
+
+ if (!empty($csv)) {
+ foreach ($csv[0] as $csv_row) {
+ $csv_row = explode('|', $csv_row);
+ $csv_fields[] = $csv_row[0];
+ }
+ }
+
+ $csv_fields = array_values(array_unique($csv_fields));
+
+ return array_diff($required, $csv_fields);
+ }
+
+ /**
+ * Get entity type fields.
+ *
+ * @param string $entity_type
+ * Entity type.
+ * @param string|null $entity_type_bundle
+ * Entity type bundle.
+ *
+ * @return array
+ * Entity type fields.
+ */
+ protected function getEntityTypeFields(string $entity_type, string $entity_type_bundle = NULL) {
+ $fields = [];
+
+ if (!$entity_type_bundle) {
+ $entity_type_bundle = key($this->entityBundleInfo()->getBundleInfo($entity_type));
+ }
+
+ $entity_fields = $this->entityFieldManager()->getFieldDefinitions($entity_type, $entity_type_bundle);
+ foreach ($entity_fields as $entity_field) {
+ $fields['fields'][] = $entity_field->getName();
+ if ($entity_field->isRequired()) {
+ $fields['required'][] = $entity_field->getName();
+ }
+ }
+ return $fields;
+ }
+
+ /**
+ * Display an error due to missing fields.
+ *
+ * @param array $required
+ * The missing required fields.
+ * @param bool $batch
+ * Whether or not we are in the midst of a batch operation.
+ *
+ * @throws \Exception
+ */
+ public function displayErrorMissingFields(array $required, bool $batch) {
+ if ($batch) {
+ $render = [
+ '#theme' => 'item_list',
+ '#items' => $required,
+ ];
+
+ $this->messenger()->addError($this->t('Your CSV has missing required fields: @fields', ['@fields' => $this->renderer()->render($render)]));
+ }
+ else {
+ throw new \Exception('Your CSV has missing required fields: ' . implode(', ', $required));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCsvByFilePath(string $filepath, string $delimiter) {
+ $return = [];
+
+ if (($csv = fopen($filepath, 'r')) !== FALSE) {
+ while (($row = fgetcsv($csv, 0, $delimiter)) !== FALSE) {
+ $return[] = $row;
+ }
+
+ fclose($csv);
+ }
+ return $return;
+ }
+
+ /**
+ * Import data from a CSV file.
+ *
+ * This is used by Drupal\csv_importer\Form\ImporterForm::submit(). You can
+ * also call this in code as follows:
+ *
+ * \Drupal::service('plugin.manager.importer')->import('node', 'article',
+ * '/full/path/to/my/csv/file.csv', ',', 'node_importer');
+ *
+ * @param string $entity_type
+ * An entity type such as "node".
+ * @param mixed $entity_type_bundle
+ * An entity type bundle such as "article" or NULL.
+ * @param string $filepath
+ * A CSV filepath such as temporary://file.csv or /full/path/to/file.csv.
+ * @param string $delimiter
+ * A delimiter such as ','.
+ * @param string $plugin_id
+ * A plugin ID such as 'node_importer'.
+ * @param bool $batch
+ * Use TRUE if this is run from the GUI and FALSE if it's from from
+ * code.
+ */
+ public function import(string $entity_type, $entity_type_bundle, string $filepath, string $delimiter, string $plugin_id, bool $batch = FALSE) {
+ $csv_parse = $this->getCsvByFilePath($filepath, $delimiter);
+ $entity_fields = $this->getEntityTypeFields($entity_type, $entity_type_bundle);
+ if ($required = $this->getEntityTypeMissingFields($entity_type, $entity_fields['required'], $csv_parse)) {
+ $this->displayErrorMissingFields($required, $batch);
+ }
+ else {
+ $importer = $this->createInstance($plugin_id, [
+ 'csv' => $csv_parse,
+ 'csv_filepath' => $filepath,
+ 'entity_type' => $entity_type,
+ 'entity_type_bundle' => $entity_type_bundle,
+ 'fields' => $entity_fields['fields'],
+ ]);
+ if ($batch) {
+ $importer->process();
+ }
+ else {
+ $importer->processNow();
+ }
+ }
+ }
+
}