Adds module config form for admins.
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
# Settings form
|
||||||
|
cdesai_migrate.settings:
|
||||||
|
title: 'Demo Migrations'
|
||||||
|
description: 'Configure Demo migration settings.'
|
||||||
|
route_name: cdesai_migrate.settings
|
||||||
|
parent: system.admin_config_development
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
cdesai_migrate.settings:
|
||||||
|
path: '/admin/config/development/cdesai/migrate'
|
||||||
|
defaults:
|
||||||
|
_form: '\Drupal\cdesai_migrate\Form\CDesaiMigrateSettings'
|
||||||
|
_title: 'Demo Migrations'
|
||||||
|
requirements:
|
||||||
|
_permission: 'administer demo migrate'
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration form.
|
||||||
|
*/
|
||||||
|
interface DemoMigrateConstantsInterface {
|
||||||
|
const CONFIG_NAME = 'cdesai_migrate.settings';
|
||||||
|
|
||||||
|
const FILE_TYPES = [
|
||||||
|
'spreadsheet' => ['csv'],
|
||||||
|
'archive' => ['zip'],
|
||||||
|
'text' => ['txt'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const SOURCES = [
|
||||||
|
'cdesai_migrate_basic_pages_source' => [
|
||||||
|
'label' => 'Basic Pages Source',
|
||||||
|
'directory' => 'private://migration/sources',
|
||||||
|
'description' => 'File must be a CSV file.',
|
||||||
|
'source_file_type' => 'spreadsheet',
|
||||||
|
'required_columns' => [
|
||||||
|
'page',
|
||||||
|
'skip_migration',
|
||||||
|
'scrapped',
|
||||||
|
'category',
|
||||||
|
'title',
|
||||||
|
'legacy_url',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'cdesai_migrate_profile_pages_source' => [
|
||||||
|
'label' => 'Profile Pages Source',
|
||||||
|
'directory' => 'private://migration/sources',
|
||||||
|
'description' => 'File must be a CSV file.',
|
||||||
|
'source_file_type' => 'spreadsheet',
|
||||||
|
'required_columns' => [
|
||||||
|
'page',
|
||||||
|
'skip_migration',
|
||||||
|
'scrapped',
|
||||||
|
'position',
|
||||||
|
'title',
|
||||||
|
'legacy_url',
|
||||||
|
'year_birth',
|
||||||
|
'year_death',
|
||||||
|
'years_in_office',
|
||||||
|
'agency',
|
||||||
|
'image_url',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'cdesai_migrate_paragraphs_source' => [
|
||||||
|
'label' => 'Paragraphs Source',
|
||||||
|
'directory' => 'private://migration/sources',
|
||||||
|
'description' => 'File must be a ZIP archive containing one or more .txt files.',
|
||||||
|
'source_file_type' => 'archive',
|
||||||
|
'archive_content_file_types' => 'text',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\cdesai_migrate\Form;
|
||||||
|
|
||||||
|
use Drupal\cdesai_migrate\DemoMigrateConstantsInterface;
|
||||||
|
use Drupal\Core\Archiver\ArchiverManager;
|
||||||
|
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||||
|
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||||
|
use Drupal\Core\File\Exception\FileException;
|
||||||
|
use Drupal\Core\File\FileSystemInterface;
|
||||||
|
use Drupal\Core\Form\ConfigFormBase;
|
||||||
|
use Drupal\Core\Form\FormStateInterface;
|
||||||
|
use Drupal\Core\StreamWrapper\StreamWrapperManager;
|
||||||
|
use Drupal\file\Entity\File;
|
||||||
|
use Drupal\file\FileInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration form.
|
||||||
|
*/
|
||||||
|
class CDesaiMigrateSettings extends ConfigFormBase {
|
||||||
|
const FORM_ID = 'cdesai_migrate_settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||||
|
*/
|
||||||
|
protected ModuleHandlerInterface $moduleHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file system.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\File\FileSystemInterface
|
||||||
|
*/
|
||||||
|
protected FileSystemInterface $fileSystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The archive manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Archiver\ArchiverManager
|
||||||
|
*/
|
||||||
|
protected ArchiverManager $archiveManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a ThemeSettingsForm object.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||||
|
* The factory for configuration objects.
|
||||||
|
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||||
|
* The module handler instance to use.
|
||||||
|
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||||
|
* The file system.
|
||||||
|
* @param \Drupal\Core\Archiver\ArchiverManager $archive_manager
|
||||||
|
* The archive manager.
|
||||||
|
*/
|
||||||
|
public function __construct(ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, FileSystemInterface $file_system, ArchiverManager $archive_manager) {
|
||||||
|
parent::__construct($config_factory);
|
||||||
|
|
||||||
|
$this->moduleHandler = $module_handler;
|
||||||
|
$this->fileSystem = $file_system;
|
||||||
|
$this->archiveManager = $archive_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function create(ContainerInterface $container) {
|
||||||
|
return new static(
|
||||||
|
$container->get('config.factory'),
|
||||||
|
$container->get('module_handler'),
|
||||||
|
$container->get('file_system'),
|
||||||
|
$container->get('plugin.manager.archiver')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function getEditableConfigNames(): array {
|
||||||
|
return [
|
||||||
|
DemoMigrateConstantsInterface::CONFIG_NAME,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFormId(): string {
|
||||||
|
return self::FORM_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function buildForm(array $form, FormStateInterface $form_state): array {
|
||||||
|
$configs = $this->config(DemoMigrateConstantsInterface::CONFIG_NAME);
|
||||||
|
|
||||||
|
foreach (DemoMigrateConstantsInterface::SOURCES as $sourceKey => $source) {
|
||||||
|
|
||||||
|
$currentPath = $configs->get($sourceKey . '_path');
|
||||||
|
$text = $this->t('Upload a file');
|
||||||
|
if ($currentPath && $this->validatePath($currentPath)) {
|
||||||
|
$text = $currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
$form[$sourceKey . '_current_path'] = [
|
||||||
|
'#type' => 'markup',
|
||||||
|
'#markup' =>
|
||||||
|
"<strong>Current {$source['label']} file:</strong> " . $text,
|
||||||
|
];
|
||||||
|
|
||||||
|
$form[$sourceKey . '_upload'] = [
|
||||||
|
'#type' => 'file',
|
||||||
|
'#title' => $this->t('Upload %title file', ['%title' => $source['label']]),
|
||||||
|
'#description' => $source['description'],
|
||||||
|
'#upload_validators' => [
|
||||||
|
'file_validate_extensions' => DemoMigrateConstantsInterface::FILE_TYPES[$source['source_file_type']],
|
||||||
|
],
|
||||||
|
'#suffix' => '<hr/>',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::buildForm($form, $form_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||||
|
parent::validateForm($form, $form_state);
|
||||||
|
|
||||||
|
if ($this->moduleHandler->moduleExists('file')) {
|
||||||
|
foreach (DemoMigrateConstantsInterface::SOURCES as $sourceKey => $source) {
|
||||||
|
// Validate file only if a new file was uploaded.
|
||||||
|
$upload_key = $sourceKey . '_upload';
|
||||||
|
$file = _file_save_upload_from_form($form[$upload_key], $form_state, 0, FileSystemInterface::EXISTS_REPLACE);
|
||||||
|
if ($file) {
|
||||||
|
if ($source['source_file_type'] === 'archive') {
|
||||||
|
$contentFileExts = DemoMigrateConstantsInterface::FILE_TYPES[$source['archive_content_file_types']];
|
||||||
|
$this->validateArchiveFile($upload_key, $contentFileExts, $file, $form_state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->validateCsvFile($upload_key, $source['required_columns'], $file, $form_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||||
|
parent::submitForm($form, $form_state);
|
||||||
|
|
||||||
|
$configs = $this->config(DemoMigrateConstantsInterface::CONFIG_NAME);
|
||||||
|
|
||||||
|
// Exclude unnecessary elements before saving.
|
||||||
|
$form_state->cleanValues();
|
||||||
|
$values = $form_state->getValues();
|
||||||
|
|
||||||
|
foreach (DemoMigrateConstantsInterface::SOURCES as $sourceKey => $source) {
|
||||||
|
// Check for a newly uploaded file.
|
||||||
|
$upload_key = $sourceKey . '_upload';
|
||||||
|
$path_key = $sourceKey . '_path';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isset($values[$upload_key]) && $values[$upload_key] instanceof File) {
|
||||||
|
$this->fileSystem->mkdir($source['directory'], NULL, TRUE);
|
||||||
|
$filename = $this->fileSystem->copy($values[$upload_key]->getFileUri(), $source['directory'], FileSystemInterface::EXISTS_RENAME);
|
||||||
|
$configs->set($path_key, $filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileException $e) {
|
||||||
|
// Ignore.
|
||||||
|
$this->logger('Demo Migration')->error($e->getMessage());
|
||||||
|
$this->messenger()->addError($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$configs->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for the cdesai_migrate_settings form.
|
||||||
|
*
|
||||||
|
* Attempts to validate normal system paths, paths relative to the public
|
||||||
|
* files directory, or stream wrapper URIs. If the given path is any of the
|
||||||
|
* above, returns a valid path or URI that the theme system can display.
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* A path relative to the Drupal root or to the public files directory, or
|
||||||
|
* a stream wrapper URI.
|
||||||
|
*
|
||||||
|
* @return false|string
|
||||||
|
* A valid path that can be displayed through the theme system, or FALSE if
|
||||||
|
* the path could not be validated.
|
||||||
|
*/
|
||||||
|
protected function validatePath(string $path) {
|
||||||
|
// Absolute local file paths are invalid.
|
||||||
|
if ($this->fileSystem->realpath($path) == $path) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A path relative to the Drupal root or a fully qualified URI is valid.
|
||||||
|
if (is_file($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepend 'private://' for relative file paths within private filesystem.
|
||||||
|
if (StreamWrapperManager::getScheme($path) === FALSE) {
|
||||||
|
$path = 'private://' . $path;
|
||||||
|
|
||||||
|
if (is_file($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the archive file.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* Form field key.
|
||||||
|
* @param array $allowed_file_extensions
|
||||||
|
* File extensions allowed in the archive.
|
||||||
|
* @param \Drupal\file\FileInterface $file
|
||||||
|
* The file being uploaded.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
*/
|
||||||
|
private function validateArchiveFile(string $key, array $allowed_file_extensions, FileInterface $file, FormStateInterface $form_state) {
|
||||||
|
$filepath = $this->fileSystem->realpath($file->getFileUri());
|
||||||
|
/** @var \Drupal\Core\Archiver\ArchiverInterface $archiver */
|
||||||
|
$archiver = $this->archiveManager->getInstance([
|
||||||
|
'filepath' => $filepath,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($archiver && !empty($archiver->listContents())) {
|
||||||
|
$otherExt = FALSE;
|
||||||
|
foreach ($archiver->listContents() as $fileInArchive) {
|
||||||
|
$ext = pathinfo($fileInArchive, PATHINFO_EXTENSION);
|
||||||
|
if (!in_array($ext, $allowed_file_extensions)) {
|
||||||
|
$otherExt = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($otherExt) {
|
||||||
|
$form_state->setErrorByName($key, $this->t('Archive contains one or more files with an invalid extension. Allowed extensions are: %ext', [
|
||||||
|
'%ext' => implode(', ', $allowed_file_extensions),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Put the temporary file in form_values so we can save it
|
||||||
|
// on submit.
|
||||||
|
$form_state->setValue($key, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$form_state->setErrorByName($key, $this->t('Not a valid archive file.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the CSV file.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* Form field key.
|
||||||
|
* @param array $required_columns
|
||||||
|
* An array column keys that should be in the CSV.
|
||||||
|
* @param \Drupal\file\FileInterface $file
|
||||||
|
* The file being uploaded.
|
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||||
|
* The current state of the form.
|
||||||
|
*/
|
||||||
|
private function validateCsvFile(string $key, array $required_columns, FileInterface $file, FormStateInterface $form_state) {
|
||||||
|
$readFile = FALSE;
|
||||||
|
|
||||||
|
if (($handle = fopen($file->getFileUri(), 'r')) !== FALSE) {
|
||||||
|
$row = fgetcsv($handle, 1000);
|
||||||
|
if ($row !== FALSE) {
|
||||||
|
$readFile = TRUE;
|
||||||
|
|
||||||
|
// Sanitize keys in the header.
|
||||||
|
$header = array_map([__CLASS__, 'sanitizeKey'], $row);
|
||||||
|
// Check for required keys.
|
||||||
|
if ($required_columns) {
|
||||||
|
$missingRequiredKeys = array_diff($required_columns, $header);
|
||||||
|
if (!empty($missingRequiredKeys)) {
|
||||||
|
$form_state->setErrorByName($key, $this->t('CSV file is missing required columns: %columns', [
|
||||||
|
'%columns' => implode(', ', $missingRequiredKeys),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Put the temporary file in form_values so we can save it
|
||||||
|
// on submit.
|
||||||
|
$form_state->setValue($key, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$readFile) {
|
||||||
|
$form_state->setErrorByName($key, $this->t('Unable to read from the file.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove whitespaces form a string and converts it to lowercase.
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* A string to sanitize.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* A sanitized string.
|
||||||
|
*/
|
||||||
|
public static function sanitizeKey(string $key): string {
|
||||||
|
$key = trim($key);
|
||||||
|
$key = str_replace(' ', '_', $key);
|
||||||
|
return strtolower($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user