path_redirect_import-8.x-1.0-beta4/src/Form/MigrateRedirectForm.php
src/Form/MigrateRedirectForm.php
<?php namespace Drupal\path_redirect_import\Form; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; use Drupal\file\FileRepositoryInterface; use Drupal\migrate\MigrateMessage; use Drupal\migrate\Plugin\MigrationInterface; use Drupal\migrate\Plugin\MigrationPluginManagerInterface; use Drupal\migrate_tools\MigrateBatchExecutable; use Drupal\path_redirect_import\MigratePluginTrait; use Drupal\redirect\Entity\Redirect; use League\Csv\Reader; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides a form for migrating redirects. * * @ingroup path_redirect_import */ class MigrateRedirectForm extends FormBase { use MigratePluginTrait; use SampleCsvFormTrait; const MIGRATE_FILE_PATH = 'temporary://path_redirect_import/migrate.csv'; /** * The tempstore object. * * @var \Drupal\Core\TempStore\SharedTempStore */ protected $privateTempStore; /** * The current user. * * @var \Drupal\Core\Session\AccountInterface */ protected $currentUser; /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * The file repository. * * @var \Drupal\file\FileRepositoryInterface */ protected $fileRepository; /** * Constructs a MigrateRedirectForm object. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager * The plugin manager for config entity-based migrations. * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory * The tempstore factory. * @param \Drupal\Core\Session\AccountInterface $current_user * Current user. * @param \Drupal\file\FileRepositoryInterface $file_repository * The file repository. */ public function __construct( EntityTypeManagerInterface $entity_type_manager, MigrationPluginManagerInterface $migration_plugin_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user, FileRepositoryInterface $file_repository ) { $this->entityTypeManager = $entity_type_manager; $this->migrationPluginManager = $migration_plugin_manager; $this->privateTempStore = $temp_store_factory->get('redirect_multiple_delete_confirm'); $this->currentUser = $current_user; $this->fileRepository = $file_repository; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('entity_type.manager'), $container->get('plugin.manager.migration'), $container->get('tempstore.private'), $container->get('current_user'), $container->get('file.repository') ); } /** * {@inheritdoc} */ public function getFormId() { return 'migrate_redirect_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { $form['table'] = $this->getSampleCsvTable($this->t('Please upload a CSV file following this pattern to migrate the redirect data:')); $form['spreadsheet'] = [ '#type' => 'managed_file', '#title' => $this->t('CSV File'), '#description' => $this->t('The CSV containing the redirect data in the expected format'), '#upload_validators' => [ 'file_validate_extensions' => ['csv'], ], '#upload_location' => 'temporary://path_redirect_import', '#weight' => 1, ]; $form['delete'] = [ '#type' => 'checkbox', '#title' => $this->t('Delete redirects defined in the spreadsheet'), '#description' => $this->t('This action can be undone'), '#weight' => 2, ]; $form['actions'] = [ '#type' => 'actions', '#weight' => 100, ]; $form['actions']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Migrate data'), ]; return $form; } /** * {@inheritdoc} */ public function validateForm(array &$form, FormStateInterface $form_state) { $input = $form_state->getUserInput(); if (isset($input['op'])) { $fid = reset($form_state->getValue('spreadsheet')); if (!$fid) { $form_state->setErrorByName('spreadsheet', $this->t('Unable to load the file. Please upload it again')); return; } /** @var \Drupal\file\Entity\File $file */ $file = $this->entityTypeManager->getStorage('file')->load($fid); $reader = Reader::createFromPath($file->getFileUri(), 'r')->setHeaderOffset(0); $removeFile = FALSE; foreach ($reader as $key => $record) { $csvLine = $key + 1; // Validate if the headers in the CSV file have the correct label. if (!isset($record['source']) || !isset($record['destination']) || !isset($record['language']) || !isset($record['status_code'])) { $form_state->setErrorByName($key, 'Header line should follow the following labels: source,destination,language,status_code'); $file->delete(); return; } // Validate character encoding. if (mb_check_encoding($record['source'], 'UTF-8') === FALSE || mb_check_encoding($record['destination'], 'UTF-8') === FALSE || mb_check_encoding($record['language'], 'UTF-8') === FALSE || mb_check_encoding($record['status_code'], 'UTF-8') === FALSE) { $removeFile = TRUE; $csvHtml = $this->t('Line @line in @label contains wrong character(s)', [ '@line' => $csvLine, '@label' => $file->label(), ]); $csvHtml .= '<br/>' . print_r($record, TRUE); $form_state->setErrorByName($key, $csvHtml); } if (in_array(NULL, $record, TRUE) || in_array('', $record, TRUE)) { $removeFile = TRUE; $csvHtml = $this->t('Line @line in @label contains empty/null value(s)', [ '@line' => $csvLine, '@label' => $file->label(), ]); $csvHtml .= '<br/>' . print_r($record, TRUE); $form_state->setErrorByName($key, $csvHtml); } // Use ltrim to compare url with /url. if (trim($record['source']) === ltrim(trim($record['destination']), '/')) { $removeFile = TRUE; $csvHtml = $this->t('Line @line in @label contains the same URL destination as source', [ '@line' => $csvLine, '@label' => $file->label(), ]); $csvHtml .= '<br/>' . print_r($record, TRUE); $form_state->setErrorByName($key, $csvHtml); } } if ($removeFile) { $file->delete(); } } } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $fid = $form_state->getValue(['spreadsheet', 0]); $file = $this->processSpreadsheet($fid); if ($file) { if ($form_state->getValue('delete') === 1) { $this->deleteRedirectData($form_state); } else { $this->migrateRedirectData(); } } else { $this->messenger()->addError('Unable to parse the file. Please try again.'); } } /** * Imports the redirect data using Migrate. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ protected function migrateRedirectData() { $migration = $this->migrationPlugin(); $migration->setStatus(MigrationInterface::STATUS_IDLE); $migrateMessage = new MigrateMessage(); $options = [ 'limit' => 0, 'update' => 1, 'force' => 0, ]; $executable = new MigrateBatchExecutable($migration, $migrateMessage, $options); $executable->batchImport(); } /** * Loads the Redirects to delete and redirects to the Deletion confirm form. * * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException * @throws \Drupal\Core\TempStore\TempStoreException * @throws \Drupal\migrate\MigrateException * @throws \League\Csv\Exception */ protected function deleteRedirectData(FormStateInterface $form_state) { $redirects = $this->redirectsToDelete(); $this->privateTempStore->set($this->currentUser->id(), $redirects); $options = [ 'query' => $this->getDestinationArray(), ]; $form_state->setRedirect('entity.redirect.multiple_delete_confirm', [], $options); } /** * Process the uploaded spreadsheet to convert it to a Migrate ready format. * * @param int $fid * The spreadsheet file ID. * * @return \Drupal\file\FileInterface|false * Resulting file entity for success, or false in the event of an error. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException */ protected function processSpreadsheet(int $fid) { /** @var \Drupal\file\Entity\File $file */ $file = $this->entityTypeManager->getStorage('file')->load($fid); return $this->fileRepository->move($file, self::MIGRATE_FILE_PATH, FileSystemInterface::EXISTS_REPLACE); } /** * Iterates over the CSV file to load the list of redirects to delete. * * @return \Drupal\redirect\Entity\Redirect[] * Array containing the Redirect entities to delete. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException * @throws \Drupal\migrate\MigrateException * @throws \League\Csv\Exception */ protected function redirectsToDelete() { $redirects_to_delete = []; $storage = $this->entityTypeManager->getStorage('redirect'); $header = $this->getReader()->getHeader(); $records = $this->getReader()->getRecords($header); foreach ($records as $record) { $parsed_url = UrlHelper::parse(urldecode($record['source'])); $path = $parsed_url['path'] ?? NULL; $query = $parsed_url['query'] ?? NULL; $hash = Redirect::generateHash($path, $query, $record['language']); // Search for redirects by hash. $redirects = $storage->loadByProperties(['hash' => $hash]); $redirects_to_delete = array_merge($redirects_to_delete, $redirects); } return $redirects_to_delete; } /** * Get the CSV reader. * * @return \League\Csv\Reader * The reader. * * @throws \Drupal\migrate\MigrateException * @throws \League\Csv\Exception */ protected function getReader() { $migration_plugin = $this->migrationPlugin(); $configuration = $migration_plugin->getSourcePlugin()->getConfiguration(); $reader = $this->createReader(); $reader->setDelimiter($configuration['delimiter']); $reader->setEnclosure($configuration['enclosure']); $reader->setEscape($configuration['escape']); $reader->setHeaderOffset($configuration['header_offset']); return $reader; } /** * Construct a new CSV reader. * * @return \League\Csv\Reader * The reader. */ protected function createReader() { return Reader::createFromStream(fopen(self::MIGRATE_FILE_PATH, 'r')); } }