Adds custom migration source and process plugins.
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\migrate\MigrateSkipRowException;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drush\Drush;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a 'Extract File Contents' migrate process plugin.
|
||||||
|
*
|
||||||
|
* Extracts contents of a file.
|
||||||
|
*
|
||||||
|
* @MigrateProcessPlugin(
|
||||||
|
* id = "extract_file_contents"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class ExtractFileContents extends ProcessPluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||||
|
$error = FALSE;
|
||||||
|
$page = $row->get('page');
|
||||||
|
|
||||||
|
if (is_null($value) || trim($value) === '') {
|
||||||
|
$error = "Skipping because content filepath is empty for {$page}.";
|
||||||
|
}
|
||||||
|
elseif (!is_file($value)) {
|
||||||
|
$error = "Skipping because content file is missing for {$page}.";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$contents = file_get_contents($value);
|
||||||
|
if ($contents !== FALSE) {
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = "Skipping because content file is unreadable for {$page}.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
Drush::output()->writeln($error);
|
||||||
|
throw new MigrateSkipRowException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\MigrateException;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\paragraphs\Entity\Paragraph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a 'Extract Paragraph' migrate process plugin.
|
||||||
|
*
|
||||||
|
* Extracts content of field_body from a paragraph entity.
|
||||||
|
*
|
||||||
|
* @MigrateProcessPlugin(
|
||||||
|
* id = "extract_cdesai_migrate_paragraph",
|
||||||
|
* handle_multiples = TRUE
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class ExtractFromParagraph extends ProcessPluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||||
|
if (!is_array($value)) {
|
||||||
|
throw new MigrateException(sprintf("Input should be an array, instead it was of type '%s'", gettype($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($value) != 2) {
|
||||||
|
throw new MigrateException(sprintf("Input array should have exactly two (2) elements, instead it had '%s'", count($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$paragraph = Paragraph::load($value[0]);
|
||||||
|
|
||||||
|
if ($paragraph->hasField('field_body')) {
|
||||||
|
$body = $paragraph->get('field_body');
|
||||||
|
$item = $body->get(0);
|
||||||
|
|
||||||
|
if ($item) {
|
||||||
|
return $item->getValue()['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new MigrateException("Paragraph does not have field_body.");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MigrateException("Unable to find paragraph.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\process;
|
||||||
|
|
||||||
|
use Drupal\Component\Plugin\Exception\PluginException;
|
||||||
|
use Drupal\migrate\MigrateSkipRowException;
|
||||||
|
use Drupal\migrate\Row;
|
||||||
|
use Drupal\migrate\ProcessPluginBase;
|
||||||
|
use Drupal\migrate\MigrateExecutableInterface;
|
||||||
|
use Drush\Drush;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a 'Generate Redirect Uri' migrate process plugin.
|
||||||
|
*
|
||||||
|
* Generates a redirect URI from the legacy url.
|
||||||
|
*
|
||||||
|
* @MigrateProcessPlugin(
|
||||||
|
* id = "generate_redirect_uri"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class GenerateRedirectUri extends ProcessPluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||||
|
*/
|
||||||
|
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||||
|
$redirect = '';
|
||||||
|
|
||||||
|
if (is_null($value) || trim($value) === '') {
|
||||||
|
$error = "Skipping because legacy_url is null or empty.";
|
||||||
|
Drush::output()->writeln($error);
|
||||||
|
throw new MigrateSkipRowException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \Drupal\node\NodeStorageInterface $nodeStorage */
|
||||||
|
try {
|
||||||
|
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
|
||||||
|
$nodes = $nodeStorage->loadByProperties([
|
||||||
|
'field_legacy_url' => trim($value),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($nodes)) {
|
||||||
|
$redirect = 'entity:node/' . reset($nodes)->id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (PluginException $e) {
|
||||||
|
$redirect = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$redirect) {
|
||||||
|
$error = sprintf('Skipped... Unable to map legacy url "%s" to a node.', $value);
|
||||||
|
Drush::output()->writeln($error);
|
||||||
|
throw new MigrateSkipRowException($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Drush::output()->writeln($redirect);
|
||||||
|
return $redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\source;
|
||||||
|
|
||||||
|
use Drupal\cdesai_migrate\DemoMigrateConstantsInterface;
|
||||||
|
use Drupal\Core\Url;
|
||||||
|
use Drupal\migrate\Plugin\MigrationInterface;
|
||||||
|
use Drupal\migrate_source_csv\Plugin\migrate\source\CSV;
|
||||||
|
use League\Csv\Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source for CSV files (overridden to make source path dynamic).
|
||||||
|
*
|
||||||
|
* @see \Drupal\migrate_source_csv\Plugin\migrate\source\CSV
|
||||||
|
*
|
||||||
|
* Available configuration options:
|
||||||
|
* @see \Drupal\migrate_source_csv\Plugin\migrate\source\CSV for all options.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Example with minimal options:
|
||||||
|
* @code
|
||||||
|
* source:
|
||||||
|
* plugin: cdesai_migrate_csv
|
||||||
|
* source_config_key: cdesai_migrate_basic_pages_source
|
||||||
|
* ids: [id]
|
||||||
|
* delimiter: '|'
|
||||||
|
* enclosure: "'"
|
||||||
|
* escape: '`'
|
||||||
|
* header_offset: 0
|
||||||
|
* fields:
|
||||||
|
* -
|
||||||
|
* name: id
|
||||||
|
* label: ID
|
||||||
|
* -
|
||||||
|
* name: country
|
||||||
|
* label: Country
|
||||||
|
*
|
||||||
|
* @MigrateSource(
|
||||||
|
* id = "cdesai_migrate_csv",
|
||||||
|
* source_module = "cdesai_migrate"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class CDesaiCSV extends CSV {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||||
|
$configuration['path'] = 'provided by the configs';
|
||||||
|
|
||||||
|
// Source config key is required.
|
||||||
|
if (empty($this->configuration['source_config_key'])) {
|
||||||
|
throw new \InvalidArgumentException('You must declare the "source_config_key" in your source settings.');
|
||||||
|
}
|
||||||
|
// Source config key must be one of the valid values.
|
||||||
|
elseif (!array_key_exists($this->configuration['source_config_key'], DemoMigrateConstantsInterface::SOURCES)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid value for "source_config_key".');
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string representing the source file path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The file path.
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getSourceFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new CSV reader.
|
||||||
|
*
|
||||||
|
* @return \League\Csv\Reader
|
||||||
|
* The reader.
|
||||||
|
*/
|
||||||
|
protected function createReader(): Reader {
|
||||||
|
$path = $this->getSourceFileName();
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
throw new \RuntimeException(sprintf('File "%s" was not found.', $path));
|
||||||
|
}
|
||||||
|
$csv = fopen($path, 'r');
|
||||||
|
if (!$csv) {
|
||||||
|
throw new \RuntimeException(sprintf('File "%s" could not be opened.', $path));
|
||||||
|
}
|
||||||
|
return Reader::createFromStream($csv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get source path from configs.
|
||||||
|
*/
|
||||||
|
private function getSourceFileName(): string {
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
return "REDACTED";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Plugin\migrate\source;
|
||||||
|
|
||||||
|
use Drupal\cdesai_migrate\DemoMigrateConstantsInterface;
|
||||||
|
use Drupal\Core\Url;
|
||||||
|
use Drupal\migrate\MigrateException;
|
||||||
|
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
|
||||||
|
use Drupal\migrate\Plugin\MigrationInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source plugin to import paragraphs from content files.
|
||||||
|
*
|
||||||
|
* @MigrateSource(
|
||||||
|
* id = "cdesai_paragraph_migrate_source"
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class CDesaiParagraphMigrateSource extends SourcePluginBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The paragraph contents archive file containing one or more text files.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $paragraphsSourceFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CSV source files for pages.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected array $pageSourceFiles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
|
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||||
|
$configs = \Drupal::config(DemoMigrateConstantsInterface::CONFIG_NAME);
|
||||||
|
$this->paragraphsSourceFile = (string) $configs->get('cdesai_migrate_paragraphs_source_path');
|
||||||
|
$this->pageSourceFiles = [
|
||||||
|
(string) $configs->get('cdesai_migrate_articles_source_path'),
|
||||||
|
(string) $configs->get('cdesai_migrate_basic_pages_source_path'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getIds(): array {
|
||||||
|
return [
|
||||||
|
'page' => [
|
||||||
|
'type' => 'string',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function fields(): array {
|
||||||
|
return [
|
||||||
|
'page' => $this->t("Page"),
|
||||||
|
'skip_migration' => $this->t('Skip Migration'),
|
||||||
|
'scrapped' => $this->t('Scrapped Content'),
|
||||||
|
'content_file' => $this->t('Paragraph Content File'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
$this->initializeIterator();
|
||||||
|
|
||||||
|
return "Content Archive: \n\t{$this->paragraphsSourceFile} \n"
|
||||||
|
. "Page Metadata Files:\n\t" . implode("\n\t", $this->pageSourceFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
protected function initializeIterator() {
|
||||||
|
$this->requiredFilesExist();
|
||||||
|
$pages = $this->extractPageData();
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
// Migrate needs an Iterator class, not just an array.
|
||||||
|
return new \ArrayIterator($pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure all required source files exists.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
private function requiredFilesExist() {
|
||||||
|
$fileExists = TRUE;
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
if (!$fileExists) {
|
||||||
|
$fileUploadUri = Url::fromRoute('cdesai_migrate.settings')->getInternalPath();
|
||||||
|
throw new MigrateException('Upload required source files at ' . $fileUploadUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves paragraph content files from the archive.
|
||||||
|
*
|
||||||
|
* @throws \Drupal\migrate\MigrateException
|
||||||
|
*/
|
||||||
|
private function getParagraphContentFiles(string $archive_file): array {
|
||||||
|
$contentFiles = [];
|
||||||
|
$extractedDir = $this->extractArchiveFile($archive_file);
|
||||||
|
/** @var \Drupal\Core\File\FileSystem $fileSystem */
|
||||||
|
$fileSystem = \Drupal::service('file_system');
|
||||||
|
$files = $fileSystem->scanDirectory($extractedDir, '/.*txt/');
|
||||||
|
if (empty($files)) {
|
||||||
|
throw new MigrateException('There was an issue extracting files form ' . $archive_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$key = substr($file->filename, 0, strlen($file->filename) - 4);
|
||||||
|
$contentFiles[$key] = $file->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contentFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the archive in a temp directory.
|
||||||
|
*
|
||||||
|
* @param string $filepath
|
||||||
|
* The file path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* The temp directory path containing the extracted archive file.
|
||||||
|
*/
|
||||||
|
private function extractArchiveFile(string $filepath): string {
|
||||||
|
/** @var \Drupal\Core\Archiver\ArchiverInterface $archiver */
|
||||||
|
$archiveManager = \Drupal::service('plugin.manager.archiver');
|
||||||
|
$tempPath = "";
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
return 'temporary://' . $tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract page data from CSV source files.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* An associative array containing page data.
|
||||||
|
*/
|
||||||
|
private function extractPageData(): array {
|
||||||
|
$pages = [];
|
||||||
|
|
||||||
|
// CODE REDACTED
|
||||||
|
|
||||||
|
return $pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user