Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / migrate_tools / src / Form / SourceCsvForm.php
diff --git a/web/modules/contrib/migrate_tools/src/Form/SourceCsvForm.php b/web/modules/contrib/migrate_tools/src/Form/SourceCsvForm.php
new file mode 100644 (file)
index 0000000..40b5dc3
--- /dev/null
@@ -0,0 +1,453 @@
+<?php
+
+namespace Drupal\migrate_tools\Form;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Messenger\MessengerInterface;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
+use Drupal\Core\TempStore\TempStoreException;
+use Drupal\Core\Url;
+use Drupal\migrate_plus\Entity\MigrationInterface;
+use Drupal\migrate_source_csv\Plugin\migrate\source\CSV;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
+
+/**
+ * Provides an edit form for CSV source plugin column_names configuration.
+ *
+ * This means you can tell the migration which columns your data is in and no
+ * longer edit the CSV to fit the column order set in the migration or edit the
+ * migration yml itself.
+ *
+ * Changes made to the column configuration, or aliases, are stored in the
+ * private migrate_toools private store keyed by the migration plugin id. The
+ * data stored for each migrations consists of two arrays, the 'original' column
+ * aliases and the 'updated' column aliases.
+ *
+ * An addtional list of all changed migration id is kept in the store, in the
+ * key 'migrations_changed'
+ *
+ * Private Store Usage:
+ *   migrations_changed: An array of the ids of the migrations that have been
+ * changed:
+ *   [migration_id]: The original and changed values for this column assignments
+ *
+ * Format of the source configuration saved in the store.
+ * @code
+ * migration_id
+ *   original
+ *     column_index1
+ *       property 1 => label 1
+ *     column_index2
+ *       property 2 => label 2
+ *   updated
+ *     column_index1
+ *       property 2 => label 2
+ *     column_index2
+ *       property 1 => label 1
+ * @endcode
+ *
+ * Example source configuration.
+ * @code
+ * custom_migration
+ *  original
+ *   2
+ *     title => title
+ *   3
+ *     body => foo
+ *  updated
+ *   8
+ *     title => new_title
+ *   9
+ *     body => new_body
+ * @endcode
+ */
+class SourceCsvForm extends FormBase {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Plugin manager for migration plugins.
+   *
+   * @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
+   */
+  protected $migrationPluginManager;
+
+  /**
+   * The messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
+  /**
+   * Temporary store for column assignment changes.
+   *
+   * @var \Drupal\Core\TempStore\PrivateTempStoreFactory
+   */
+  protected $store;
+
+  /**
+   * The file object that reads the CSV file.
+   *
+   * @var \SplFileObject
+   */
+  protected $file = NULL;
+
+  /**
+   * The migration being examined.
+   *
+   * @var \Drupal\migrate\Plugin\MigrationInterface
+   */
+  protected $migration;
+
+  /**
+   * The migration plugin id.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The array of columns names from the CSV source plugin.
+   *
+   * @var array
+   */
+  protected $columnNames;
+
+  /**
+   * An array of options for the column select form field..
+   *
+   * @var array
+   */
+  protected $options;
+
+  /**
+   * An array of modified and original column_name source plugin configuration.
+   *
+   * @var array
+   */
+  protected $sourceConfiguration;
+
+  /**
+   * Constructs new SourceCsvForm object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection.
+   * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
+   *   The plugin manager for config entity-based migrations.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger service.
+   * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $private_store
+   *   The private store.
+   */
+  public function __construct(Connection $connection, MigrationPluginManagerInterface $migration_plugin_manager, MessengerInterface $messenger, PrivateTempStoreFactory $private_store) {
+    $this->connection = $connection;
+    $this->migrationPluginManager = $migration_plugin_manager;
+    $this->messenger = $messenger;
+    $this->store = $private_store->get('migrate_tools');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('database'),
+      $container->get('plugin.manager.migration'),
+      $container->get('messenger'),
+      $container->get('tempstore.private')
+    );
+  }
+
+  /**
+   * A custom access check.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   Run access checks for this account.
+   * @param \Drupal\migrate_plus\Entity\MigrationInterface $migration
+   *   The $migration.
+   *
+   * @return \Drupal\Core\Access\AccessResult
+   *   Allowed or forbidden, neutral if tempstore is empty.
+   */
+  public function access(AccountInterface $account, MigrationInterface $migration) {
+    try {
+      $this->migration = $this->migrationPluginManager->createInstance($migration->id(), $migration->toArray());
+    }
+    catch (PluginException $e) {
+      return AccessResult::forbidden();
+    }
+
+    if ($this->migration) {
+      if ($source = $this->migration->getSourcePlugin()) {
+        if (is_a($source, CSV::class)) {
+          return AccessResult::allowed();
+        }
+      }
+    }
+    return AccessResult::forbidden();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, MigrationInterface $migration = NULL) {
+    try {
+      // @TODO: remove this horrible config work around after
+      // https://www.drupal.org/project/drupal/issues/2986665 is fixed.
+      $this->migration = $this->migrationPluginManager->createInstance($migration->id(), $migration->toArray());
+      /** @var \Drupal\migrate_source_csv\Plugin\migrate\source\CSV $source */
+      $source = $this->migration->getSourcePlugin();
+      $source->setConfiguration($migration->toArray()['source']);
+    }
+    catch (PluginException $e) {
+      return AccessResult::forbidden();
+    }
+
+    // Get the source file after the properties are initialized.
+    $source->initializeIterator();
+    $this->file = $source->getFile();
+
+    // Set the input field options to the header row values or, if there are
+    // no such values, use an indexed array.
+    if ($this->file->getHeaderRowCount() > 0) {
+      $this->options = $this->getHeaderColumnNames();
+    }
+    else {
+      for ($i = 0; $i < $this->getFileColumnCount(); $i++) {
+        $this->options[$i] = $i;
+      }
+    }
+
+    // Set the store key to the migration id.
+    $this->id = $this->migration->getPluginId();
+
+    // Get the column names from the file or from the store, if updated
+    // values are in the store.
+    $this->sourceConfiguration = $this->store->get($this->id);
+    if (isset($this->sourceConfiguration['changed'])) {
+      if ($config = $this->sourceConfiguration['changed']) {
+        $this->columnNames = $config;
+      }
+    }
+    else {
+      // Get the calculated column names. This is either the header rows or
+      // the configuration column_name value.
+      $this->columnNames = $this->file->getColumnNames();
+      if (!isset($this->sourceConfiguration['original'])) {
+        // Save as the original values.
+        $this->sourceConfiguration['original'] = $this->columnNames;
+        $this->store->set($this->id, $this->sourceConfiguration);
+      }
+    }
+    $form['#title'] = $this->t('Column Aliases');
+
+    $form['heading'] = [
+      '#type' => 'item',
+      '#title' => $this->t(':label', [':label' => $this->migration->label()]),
+      '#description' => '<p>' . $this->t('You can change the columns to be used by this migration for each source property.') . '</p>',
+    ];
+    // Create a form field for each column in this migration.
+    foreach ($this->columnNames as $index => $data) {
+      $property_name = key($data);
+      $default_value = $index;
+      $label = $this->getLabel($this->sourceConfiguration['original'], $property_name);
+
+      $description = $this->t('Select the column where the data for <em>:label</em>, property <em>:property</em>, will be found.', [
+        ':label' => $label,
+        ':property' => $property_name,
+      ]);
+      $form['aliases'][$property_name] = [
+        '#type' => 'select',
+        '#title' => $label,
+        '#description' => $description,
+        '#options' => $this->options,
+        '#default_value' => $default_value,
+      ];
+    }
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#button_type' => 'primary',
+      '#value' => $this->t('Submit'),
+    ];
+    $form['actions']['cancel'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Cancel'),
+      '#submit' => ['::cancel'],
+      '#limit_validation_errors' => [],
+    ];
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    // Display an error message if two properties have the same source column.
+    $values = [];
+    foreach ($this->columnNames as $index => $data) {
+      $property_name = key($data);
+      $value = $form_state->getValue($property_name);
+      if (in_array($value, $values)) {
+        $form_state->setErrorByName($property_name, $this->t('Source properties can not share the same source column.'));
+      }
+      $values[] = $value;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Create a new column_names configuration.
+    $new_column_names = [];
+    foreach ($this->columnNames as $index => $data) {
+      // Keep the property name as it is used in the process pipeline.
+      $property_name = key($data);
+      // Get the new column number from the form alias field for this property.
+      $new_index = $form_state->getValue($property_name);
+      // Get the new label from the options array.
+      $new_label = $this->options[$new_index];
+      // Save using the new column number and new label.
+      $new_column_names[$new_index] = [$property_name => $new_label];
+    }
+    // Update the file columns.
+    $this->file->setColumnNames($new_column_names);
+    // Save as updated in the store.
+    $this->sourceConfiguration['changed'] = $new_column_names;
+    $this->store->set($this->id, $this->sourceConfiguration);
+
+    $changed = ($this->store->get('migrations_changed')) ? $this->store->get('migrations_changed') : [];
+    if (!in_array($this->id, $changed)) {
+      $changed[] = $this->id;
+      $this->store->set('migrations_changed', $changed);
+    }
+  }
+
+  /**
+   * Form submission handler for the 'cancel' action.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function cancel(array $form, FormStateInterface $form_state) {
+    // Restore the file columns to the original settings.
+    $this->file->setColumnNames($this->sourceConfiguration['original']);
+    // Remove this migration from the store.
+    try {
+      $this->store->delete($this->id);
+    }
+    catch (TempStoreException $e) {
+      $this->messenger->addError($e->getMessage());
+    }
+
+    $migrationsChanged = $this->store->get('migrations_changed');
+    unset($migrationsChanged[$this->id]);
+    try {
+      $this->store->set('migrations_changed', $migrationsChanged);
+    }
+    catch (TempStoreException $e) {
+      $this->messenger->addError($e->getMessage());
+    }
+    $form_state->setRedirect('entity.migration_group.list');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'migrate_tools_source_csv';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    return new Url('entity.migration_group.list');
+  }
+
+  /**
+   * Returns the header row.
+   *
+   * Use a new file handle so that CSVFileObject::current() is not executed.
+   *
+   * @return array
+   *   The header row.
+   */
+  public function getHeaderColumnNames() {
+    $row = [];
+    $fname = $this->file->getPathname();
+    $handle = fopen($fname, 'r');
+    if ($handle) {
+      fseek($handle, $this->file->getHeaderRowCount() - 1);
+      $row = fgetcsv($handle);
+      fclose($handle);
+    }
+    return $row;
+  }
+
+  /**
+   * Returns the count of fields in the header row.
+   *
+   * Use a new file handle so that CSVFileObject::current() is not executed.
+   *
+   * @return int
+   *   The number of fields in the header row.
+   */
+  public function getFileColumnCount() {
+    $count = 0;
+    $fname = $this->file->getPathname();
+    $handle = fopen($fname, 'r');
+    if ($handle) {
+      $row = fgetcsv($handle);
+      $count = count($row);
+      fclose($handle);
+    }
+    return $count;
+  }
+
+  /**
+   * Gets the label for a given property from a column_names array.
+   *
+   * @param array $column_names
+   *   An array of column_names.
+   * @param string $property_name
+   *   The property name to find a label for.
+   *
+   * @return string
+   *   The label for this property.
+   */
+  protected function getLabel(array $column_names, $property_name) {
+    $label = '';
+    foreach ($column_names as $column) {
+      foreach ($column as $key => $value) {
+        if ($key === $property_name) {
+          $label = $value;
+          break;
+        }
+      }
+    }
+    return $label;
+  }
+
+}