From d5a3ba820bac8a28b41bbc808c3f256873719d41 Mon Sep 17 00:00:00 2001 From: dpa Date: Wed, 27 Aug 2025 16:17:26 +0200 Subject: [PATCH 1/6] feat: refactorings for contao 5 --- composer.json | 27 +- src/Command/ExecuteImportCommand.php | 75 ++++- src/Controller/PoorManCronController.php | 12 +- .../EntityImportConfigContainer.php | 76 +++-- .../EntityImportQuickConfigContainer.php | 293 ++++++++++++++---- .../EntityImportSourceContainer.php | 28 +- .../HeimrichHannotEntityImportExtension.php | 2 +- .../Contao/SqlGetFromDcaEventListener.php | 7 +- src/EventListener/LoadProgressListener.php | 17 +- ...HeimrichHannotContaoEntityImportBundle.php | 3 +- src/Importer/Importer.php | 13 +- src/Resources/config/services.yml | 4 + src/Source/AbstractFileSource.php | 25 +- src/Source/AbstractSource.php | 3 +- src/Source/CSVFileSource.php | 5 +- src/Source/DatabaseSource.php | 23 +- src/Source/JSONFileSource.php | 2 +- src/Source/RSSFileSource.php | 9 +- src/Source/SourceFactory.php | 38 +-- src/Source/XmlFileSource.php | 2 +- src/Util/EntityImportUtil.php | 12 + 21 files changed, 443 insertions(+), 233 deletions(-) diff --git a/composer.json b/composer.json index 79b835d..537f45e 100644 --- a/composer.json +++ b/composer.json @@ -13,24 +13,25 @@ } ], "require": { - "php": "^7.4||^8.0", + "php": "^7.4 || ^8.0", "ausi/slug-generator": "^1.1", - "contao/core-bundle": "^4.9", + "contao/core-bundle": "^4.13 || ^5.0", + "elvanto/litemoji": "^5.2", "guzzlehttp/guzzle": "^6.0 || ^7.0", "heimrichhannot/contao-be_explanation-bundle": "^2.3", "heimrichhannot/contao-field-value-copier-bundle": "^1.1", - "heimrichhannot/contao-list_widget": "^2.1", + "heimrichhannot/contao-list-widget-bundle": "^1.5", "heimrichhannot/contao-multi-column-editor-bundle": "^2.4", - "heimrichhannot/contao-progress-bar-widget-bundle": "^0.1", - "heimrichhannot/contao-utils-bundle": "^2.135", - "symfony/cache": "^4.4 || ^5.2", - "symfony/config": "^4.4||^5.4", - "symfony/console": "^4.4||^5.4", - "symfony/dependency-injection": "^4.4||^5.4", - "symfony/event-dispatcher": "^4.4||^5.4", - "symfony/event-dispatcher-contracts": "^1||^2||^3", - "symfony/http-kernel": "^4.4||^5.4", - "symfony/stopwatch": "^4.4 || ^5.2", + "heimrichhannot/contao-progress-bar-widget-bundle": "dev-master", + "heimrichhannot/contao-utils-bundle": "^2.135 || ^3.7", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher-contracts": "^1 || ^2 || ^3", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", "terminal42/service-annotation-bundle": "^1.1" }, "require-dev": { diff --git a/src/Command/ExecuteImportCommand.php b/src/Command/ExecuteImportCommand.php index 7201414..a4e9ace 100644 --- a/src/Command/ExecuteImportCommand.php +++ b/src/Command/ExecuteImportCommand.php @@ -8,31 +8,47 @@ namespace HeimrichHannot\EntityImportBundle\Command; -use Contao\CoreBundle\Command\AbstractLockedCommand; +use Contao\CoreBundle\Framework\ContaoFramework; use HeimrichHannot\EntityImportBundle\DataContainer\EntityImportConfigContainer; use HeimrichHannot\EntityImportBundle\Importer\ImporterFactory; use HeimrichHannot\EntityImportBundle\Importer\ImporterInterface; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Lock\LockFactory; +use Symfony\Component\Lock\Store\FlockStore; +use Symfony\Component\Filesystem\Path; -class ExecuteImportCommand extends AbstractLockedCommand +class ExecuteImportCommand extends Command { - protected InputInterface $input; - protected SymfonyStyle $io; - protected ModelUtil $modelUtil; + protected InputInterface $input; + protected SymfonyStyle $io; + protected Utils $utils; protected ImporterFactory $importerFactory; + protected ContaoFramework $framework; + protected Filesystem $filesystem; + protected string $projectDir; /** * ExecuteImportCommand constructor. */ - public function __construct(ModelUtil $modelUtil, ImporterFactory $importerFactory) - { - $this->modelUtil = $modelUtil; + public function __construct( + Utils $utils, + ImporterFactory $importerFactory, + ContaoFramework $framework, + Filesystem $filesystem, + string $projectDir + ) { + $this->utils = $utils; $this->importerFactory = $importerFactory; + $this->framework = $framework; + $this->filesystem = $filesystem; + $this->projectDir = $projectDir; parent::__construct(); } @@ -52,26 +68,37 @@ protected function configure() /** * {@inheritdoc} */ - protected function executeLocked(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { + $store = new FlockStore($this->getTempDir()); + $factory = new LockFactory($store); + $lock = $factory->createLock($this->getName()); + + if (!$lock->acquire()) { + $output->writeln('The command is already running in another process.'); + + return 1; + } + $this->input = $input; - $this->io = new SymfonyStyle($input, $output); - $this->framework = $this->getContainer()->get('contao.framework'); - $this->framework->initialize(); + $this->io = new SymfonyStyle($input, $output); + $this->container->get('contao.framework')->initialize(); $this->import(); + $lock->release(); + return 0; } private function import() { - $configIds = explode(',', $this->input->getArgument('config-ids')); - $dryRun = $this->input->getOption('dry-run') ?: false; + $configIds = explode(',', $this->input->getArgument('config-ids')); + $dryRun = $this->input->getOption('dry-run') ?: false; $webCronMode = $this->input->getOption('web-cron-mode') ?: false; foreach ($configIds as $configId) { - if (null === ($configModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $configId))) { + if (null === ($configModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $configId))) { $this->io->error("Importer config with ID $configId not found."); continue; @@ -93,7 +120,7 @@ private function import() continue; } - $configModel->importStarted = time(); + $configModel->importStarted = time(); $configModel->importProgressCurrent = 0; $configModel->save(); } @@ -133,4 +160,18 @@ private function import() } } } + + /** + * Creates an installation specific folder in the temporary directory and returns its path. + */ + private function getTempDir(): string + { + $tmpDir = Path::join(sys_get_temp_dir(), md5((string)$this->projectDir)); + + if (!is_dir($tmpDir)) { + $this->filesystem->mkdir($tmpDir); + } + + return $tmpDir; + } } diff --git a/src/Controller/PoorManCronController.php b/src/Controller/PoorManCronController.php index fd136bf..6210d77 100644 --- a/src/Controller/PoorManCronController.php +++ b/src/Controller/PoorManCronController.php @@ -10,17 +10,17 @@ use HeimrichHannot\EntityImportBundle\Importer\ImporterFactory; use HeimrichHannot\EntityImportBundle\Importer\ImporterInterface; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; class PoorManCronController { protected ImporterFactory $importerFactory; - protected ModelUtil $modelUtil; + protected Utils $utils; - public function __construct(ImporterFactory $importerFactory, ModelUtil $modelUtil) + public function __construct(ImporterFactory $importerFactory, Utils $utils) { $this->importerFactory = $importerFactory; - $this->modelUtil = $modelUtil; + $this->utils = $utils; } public function runMinutely() @@ -70,7 +70,7 @@ public function runMonthly() protected function getConfigIds(string $interval): array { - $models = $this->modelUtil->findModelInstancesBy('tl_entity_import_config', + $models = $this->utils->model()->findModelInstancesBy('tl_entity_import_config', ['tl_entity_import_config.useCron=?', 'tl_entity_import_config.cronInterval=?', 'tl_entity_import_config.usePoorMansCron=?'], [true, $interval, true]); if (null === $models) { @@ -82,7 +82,7 @@ protected function getConfigIds(string $interval): array protected function run(string $id) { - if (null === ($configModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $id))) { + if (null === ($configModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $id))) { return; } diff --git a/src/DataContainer/EntityImportConfigContainer.php b/src/DataContainer/EntityImportConfigContainer.php index 27cf90d..8ba4234 100644 --- a/src/DataContainer/EntityImportConfigContainer.php +++ b/src/DataContainer/EntityImportConfigContainer.php @@ -14,13 +14,13 @@ use Contao\Database; use Contao\DataContainer; use Contao\Date; +use Contao\Image; +use Contao\Input; use Contao\StringUtil; +use Doctrine\DBAL\Connection; use HeimrichHannot\EntityImportBundle\Event\AddConfigFieldMappingPresetsEvent; use HeimrichHannot\EntityImportBundle\Importer\ImporterFactory; -use HeimrichHannot\RequestBundle\Component\HttpFoundation\Request; -use HeimrichHannot\UtilsBundle\Database\DatabaseUtil; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; -use HeimrichHannot\UtilsBundle\Url\UrlUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class EntityImportConfigContainer @@ -31,7 +31,7 @@ class EntityImportConfigContainer self::SORTING_MODE_TARGET_FIELDS, ]; - const DELETION_MODE_MIRROR = 'mirror'; + const DELETION_MODE_MIRROR = 'mirror'; const DELETION_MODE_TARGET_FIELDS = 'target_fields'; const DELETION_MODES = [ @@ -46,30 +46,24 @@ class EntityImportConfigContainer ]; const STATE_READY_FOR_IMPORT = 'ready_for_import'; - const STATE_SUCCESS = 'success'; - const STATE_FAILED = 'failed'; - - protected Request $request; - protected UrlUtil $urlUtil; - protected ModelUtil $modelUtil; - protected ImporterFactory $importerFactory; - protected DatabaseUtil $databaseUtil; + const STATE_SUCCESS = 'success'; + const STATE_FAILED = 'failed'; + + protected ImporterFactory $importerFactory; + protected Connection $connection; protected EventDispatcherInterface $eventDispatcher; + protected Utils $utils; public function __construct( - Request $request, ImporterFactory $importerFactory, - UrlUtil $urlUtil, - ModelUtil $modelUtil, - DatabaseUtil $databaseUtil, - EventDispatcherInterface $eventDispatcher + Connection $connection, + EventDispatcherInterface $eventDispatcher, + Utils $utils ) { - $this->request = $request; - $this->urlUtil = $urlUtil; - $this->modelUtil = $modelUtil; $this->importerFactory = $importerFactory; - $this->databaseUtil = $databaseUtil; + $this->connection = $connection; $this->eventDispatcher = $eventDispatcher; + $this->utils = $utils; } public function getDryRunOperation($row, $href, $label, $title, $icon, $attributes) @@ -78,7 +72,7 @@ public function getDryRunOperation($row, $href, $label, $title, $icon, $attribut return ''; } - return ''.\Image::getHtml($icon, $label).' '; + return '' . Image::getHtml($icon, $label) . ' '; } public function setPreset(?DataContainer $dc) @@ -89,17 +83,17 @@ public function setPreset(?DataContainer $dc) $dca = &$GLOBALS['TL_DCA']['tl_entity_import_config']; - $this->databaseUtil->update('tl_entity_import_config', [ + $this->connection->update('tl_entity_import_config', [ 'fieldMappingPresets' => '', - 'fieldMapping' => serialize($dca['fields']['fieldMappingPresets']['eval']['presets'][$preset]), - ], 'tl_entity_import_config.id='.$dc->id); + 'fieldMapping' => serialize($dca['fields']['fieldMappingPresets']['eval']['presets'][$preset]), + ], ['tl_entity_import_config.id=' . $dc->id]); } public function initPalette(?DataContainer $dc) { $dca = &$GLOBALS['TL_DCA'][$dc->table]; - if (null === ($configModel = $this->modelUtil->findModelInstanceByPk($dc->table, $dc->id)) || !$configModel->targetTable) { + if (null === ($configModel = $this->utils->model()->findModelInstanceByPk($dc->table, $dc->id)) || !$configModel->targetTable) { $dca['palettes']['default'] = '{general_legend},title,targetTable;'; return; @@ -123,7 +117,7 @@ public function initPalette(?DataContainer $dc) asort($presets); - $dca['fields']['fieldMappingPresets']['options'] = $options; + $dca['fields']['fieldMappingPresets']['options'] = $options; $dca['fields']['fieldMappingPresets']['eval']['presets'] = $presets; } } @@ -137,11 +131,11 @@ public function getSourceFields(?DataContainer $dc): array { $options = []; - if (null === ($configModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $dc->id))) { + if (null === ($configModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $dc->id))) { return $options; } - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $configModel->pid))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $configModel->pid))) { return $options; } @@ -155,7 +149,7 @@ public function getSourceFields(?DataContainer $dc): array if (null === $data['sourceValue']) { $options[$data['name']] = $data['name']; } else { - $options[$data['name']] = $data['name'].' ['.$data['sourceValue'].']'; + $options[$data['name']] = $data['name'] . ' [' . $data['sourceValue'] . ']'; } } @@ -166,7 +160,7 @@ public function getTargetFields(?DataContainer $dc): array { $options = []; - if (null === ($configModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $dc->id)) || !$configModel->targetTable) { + if (null === ($configModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $dc->id)) || !$configModel->targetTable) { return $options; } @@ -181,7 +175,7 @@ public function getTargetFields(?DataContainer $dc): array continue; } - $options[$field['name']] = $field['name'].' ['.$field['origtype'].']'; + $options[$field['name']] = $field['name'] . ' [' . $field['origtype'] . ']'; } return $options; @@ -199,34 +193,34 @@ public function dryRun() public function listItems(array $row): string { - return '
'.$row['title'].' ['.Date::parse(Config::get('datimFormat'), $row['dateAdded']).']
'; + return '
' . $row['title'] . ' [' . Date::parse(Config::get('datimFormat'), $row['dateAdded']) . ']
'; } private function runImport(bool $dry = false) { - $config = $this->request->getGet('id'); + $config = Input::get('id'); - if (null === ($configModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $config))) { + if (null === ($configModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $config))) { throw new \Exception(sprintf('Entity config model of ID %s not found', $config)); } - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $configModel->pid))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $configModel->pid))) { throw new \Exception(sprintf('Entity source model of ID %s not found', $configModel->pid)); } if ($configModel->useCronInWebContext) { - $configModel->importStarted = $configModel->importProgressCurrent = $configModel->importProgressTotal = $configModel->importProgressSkipped = 0; - $configModel->state = static::STATE_READY_FOR_IMPORT; + $configModel->importStarted = $configModel->importProgressCurrent = $configModel->importProgressTotal = $configModel->importProgressSkipped = 0; + $configModel->state = static::STATE_READY_FOR_IMPORT; $configModel->importProgressResult = ''; $configModel->save(); - throw new RedirectResponseException($this->urlUtil->addQueryString('act=edit', $this->urlUtil->removeQueryString(['key']))); + throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('act=edit', $this->utils->url()->removeQueryStringParameterFromUrl(['key']))); } $importer = $this->importerFactory->createInstance($configModel->id); $importer->setDryRun($dry); $result = $importer->run(); $importer->outputFinalResultMessage($result); - throw new RedirectResponseException($this->urlUtil->addQueryString('id='.$sourceModel->id, $this->urlUtil->removeQueryString(['key', 'id']))); + throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('id=' . $sourceModel->id, $this->utils->url()->removeQueryStringParameterFromUrl(['key', 'id']))); } } diff --git a/src/DataContainer/EntityImportQuickConfigContainer.php b/src/DataContainer/EntityImportQuickConfigContainer.php index 5684729..5a98d75 100644 --- a/src/DataContainer/EntityImportQuickConfigContainer.php +++ b/src/DataContainer/EntityImportQuickConfigContainer.php @@ -10,59 +10,63 @@ use Contao\Controller; use Contao\CoreBundle\Exception\RedirectResponseException; +use Contao\CoreBundle\Framework\ContaoFramework; use Contao\Database; use Contao\DataContainer; +use Contao\DcaLoader; +use Contao\Image; +use Contao\Input; +use Contao\Model; use Contao\StringUtil; use Contao\System; +use Doctrine\DBAL\Connection; use HeimrichHannot\EntityImportBundle\Event\BeforeImportEvent; use HeimrichHannot\EntityImportBundle\Event\BeforeItemImportEvent; use HeimrichHannot\EntityImportBundle\Importer\ImporterFactory; use HeimrichHannot\EntityImportBundle\Source\SourceFactory; use HeimrichHannot\EntityImportBundle\Source\SourceInterface; -use HeimrichHannot\ListWidget\ListWidget; -use HeimrichHannot\RequestBundle\Component\HttpFoundation\Request; -use HeimrichHannot\UtilsBundle\Database\DatabaseUtil; -use HeimrichHannot\UtilsBundle\Dca\DcaUtil; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; -use HeimrichHannot\UtilsBundle\Url\UrlUtil; +use HeimrichHannot\EntityImportBundle\Util\EntityImportUtil; +use HeimrichHannot\ListWidgetBundle\Widget\ListWidget; +use HeimrichHannot\UtilsBundle\StaticUtil\StaticArrayUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class EntityImportQuickConfigContainer { - protected DatabaseUtil $databaseUtil; - protected SourceFactory $sourceFactory; + const ON_DUPLICATE_KEY_IGNORE = 'IGNORE'; + const ON_DUPLICATE_KEY_UPDATE = 'UPDATE'; + + protected SourceFactory $sourceFactory; protected EventDispatcherInterface $eventDispatcher; - protected ModelUtil $modelUtil; - protected Request $request; - protected ImporterFactory $importerFactory; - protected UrlUtil $urlUtil; - protected DcaUtil $dcaUtil; + protected ImporterFactory $importerFactory; + protected Utils $utils; + protected ContaoFramework $framework; + protected Connection $connection; + protected EntityImportUtil $entityImportUtil; public function __construct( - ModelUtil $modelUtil, EventDispatcherInterface $eventDispatcher, - Request $request, ImporterFactory $importerFactory, - UrlUtil $urlUtil, - DcaUtil $dcaUtil, - DatabaseUtil $databaseUtil, - SourceFactory $sourceFactory + SourceFactory $sourceFactory, + ContaoFramework $framework, + Connection $connection, + EntityImportUtil $entityImportUtil, + Utils $utils ) { $this->eventDispatcher = $eventDispatcher; - $this->modelUtil = $modelUtil; - $this->request = $request; $this->importerFactory = $importerFactory; - $this->urlUtil = $urlUtil; - $this->dcaUtil = $dcaUtil; - $this->databaseUtil = $databaseUtil; $this->sourceFactory = $sourceFactory; + $this->utils = $utils; + $this->framework = $framework; + $this->connection = $connection; + $this->entityImportUtil = $entityImportUtil; } public function getImporterConfigs() { $options = []; - if (null === ($configs = $this->modelUtil->findAllModelInstances('tl_entity_import_config', [ + if (null === ($configs = $this->utils->model()->findModelInstancesBy('tl_entity_import_config', [], [], [ 'order' => 'tl_entity_import_config.title ASC', ]))) { return []; @@ -77,32 +81,33 @@ public function getImporterConfigs() public function getDryRunOperation($row, $href, $label, $title, $icon, $attributes) { - if (null !== ($config = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $row['importerConfig']))) { + if (null !== ($config = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $row['importerConfig']))) { if ($config->useCronInWebContext) { return ''; } } - return ''.\Image::getHtml($icon, $label).' '; + return ''.Image::getHtml($icon, $label).' '; } public function modifyDca(DataContainer $dc) { - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { return; } - if (null === ($importer = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importer = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return; } - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $importer->pid))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $importer->pid))) { return; } $dca = &$GLOBALS['TL_DCA']['tl_entity_import_quick_config']; - $this->dcaUtil->loadDc($importer->targetTable); + $loader = new DcaLoader($importer->targetTable); + $loader->load(); $targetDca = &$GLOBALS['TL_DCA'][$importer->targetTable]; @@ -154,19 +159,19 @@ public function loadCsvRowsFromCache($config, $options = [], $context = null, $d public function cacheCsvRows(DataContainer $dc) { // cache might be invalid now -> delete tl_md_recipient - $this->databaseUtil->delete('tl_entity_import_cache', 'cache_ptable=? AND cache_pid=?', ['tl_entity_import_quick_config', $dc->id]); + $this->connection->delete('tl_entity_import_cache', ['cache_ptable="tl_entity_import_quick_config" AND cache_pid=' . $dc->id]); // cache the rows - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig || !$quickImporter->fileSRC) { return; } - if (null === ($importerConfig = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importerConfig = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return; } - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $importerConfig->pid))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $importerConfig->pid))) { return; } @@ -218,7 +223,7 @@ public function cacheCsvRows(DataContainer $dc) $itemsToInsert[] = $event->getMappedItem(); } - $this->databaseUtil->doBulkInsert('tl_entity_import_cache', $itemsToInsert, [ + $this->doBulkInsert('tl_entity_import_cache', $itemsToInsert, [ 'cache_ptable' => 'tl_entity_import_quick_config', 'cache_pid' => $dc->id, ]); @@ -248,13 +253,13 @@ public function prepareCachedCsvRows($items, $config, $options = [], $context = return $itemData; } - public function getParentEntitiesAsOptions(\Contao\DataContainer $dc) + public function getParentEntitiesAsOptions(DataContainer $dc): array { - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { return []; } - if (null === ($importer = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importer = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return []; } @@ -264,9 +269,17 @@ public function getParentEntitiesAsOptions(\Contao\DataContainer $dc) return []; } - return System::getContainer()->get('huh.utils.choice.model_instance')->getCachedChoices([ - 'dataContainer' => $dca['config']['ptable'], - ]); + $options = []; + + $models = $this->utils->model()->findModelInstancesBy($dca['config']['ptable'], [], []); + + if (null !== $models) { + while ($models->next()) { + $options[$models->id] = $models->title ?: $models->headline ?: $models->id; + } + } + + return $options; } public function import() @@ -279,29 +292,30 @@ public function dryRun() $this->runImport(true); } - public function getHeaderFieldsForPreview($config, $widget, \DataContainer $dc) + public function getHeaderFieldsForPreview($config, $widget, DataContainer $dc) { - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { return []; } - if (null === ($importer = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importer = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return []; } $fields = []; foreach (StringUtil::deserialize($importer->fieldMapping, true) as $mapping) { - $fields[$mapping['columnName']] = $this->dcaUtil->getLocalizedFieldName($mapping['columnName'], $importer->targetTable); + $fields[$mapping['columnName']] = $this->entityImportUtil->getLocalizedFieldName($mapping['columnName'], $importer->targetTable); } if (Database::getInstance()->fieldExists('pid', $importer->targetTable) && $quickImporter->parentEntity) { - $this->dcaUtil->loadDc($importer->targetTable); + $loader = new DcaLoader($importer->targetTable); + $loader->load(); $dca = &$GLOBALS['TL_DCA'][$importer->targetTable]; if (isset($dca['config']['ptable'])) { - $fields = array_merge(['pid' => $this->dcaUtil->getLocalizedFieldName('pid', $importer->targetTable)], $fields); + $fields = array_merge(['pid' => $this->entityImportUtil->getLocalizedFieldName('pid', $importer->targetTable)], $fields); } } @@ -310,15 +324,15 @@ public function getHeaderFieldsForPreview($config, $widget, \DataContainer $dc) public function getItemsForPreview($config, $widget, $dc) { - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig || !$quickImporter->fileSRC) { + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig || !$quickImporter->fileSRC) { return []; } - if (null === ($importer = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importer = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return []; } - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $importer->pid))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $importer->pid))) { return []; } @@ -344,7 +358,8 @@ protected function addParentEntityToFieldMapping($quickImporter, $importer) return; } - $this->dcaUtil->loadDc($importer->targetTable); + $loader = new DcaLoader($importer->targetTable); + $loader->load(); $dca = &$GLOBALS['TL_DCA'][$importer->targetTable]; @@ -363,17 +378,17 @@ protected function addParentEntityToFieldMapping($quickImporter, $importer) private function runImport(bool $dry = false) { - $config = $this->request->getGet('id'); + $config = Input::get('id'); - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $config)) || !$quickImporter->importerConfig) { + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $config)) || !$quickImporter->importerConfig) { return; } - if (null === ($importerConfig = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importerConfig = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return; } - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $importerConfig->pid))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $importerConfig->pid))) { return; } @@ -391,12 +406,174 @@ private function runImport(bool $dry = false) $importerConfig->importProgressResult = ''; $importerConfig->save(); - throw new RedirectResponseException($this->urlUtil->addQueryString('act=edit', $this->urlUtil->removeQueryString(['key']))); + throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('act=edit', $this->utils->url()->removeQueryStringParameterFromUrl(['key']))); } $importer->setDryRun($dry); $result = $importer->run(); $importer->outputFinalResultMessage($result); - throw new RedirectResponseException($this->urlUtil->removeQueryString(['key', 'id'])); + throw new RedirectResponseException($this->utils->url()->removeQueryStringParameterFromUrl(['key', 'id'])); + } + + /** + * Bulk insert SQL of given data. + * + * @param string $table The database table, where new items should be stored inside + * @param array $data An array of values associated to its field + * @param array $fixedValues A array of fixed values associated to its field that should be set for each row as fixed values + * @param mixed $onDuplicateKey null = Throw error on duplicates, self::ON_DUPLICATE_KEY_IGNORE = ignore error duplicates (skip this entries), + * self::ON_DUPLICATE_KEY_UPDATE = update existing entries + * @param callable $callback A callback that should be triggered after each cycle, contains $arrValues of current cycle + * @param callable $itemCallback A callback to change the insert values for each items, contains $arrValues as first argument, $arrFields as + * second, $arrOriginal as third, expects an array as return value with same order as $arrFields, if no array is + * returned, insert of the row will be skipped item insert + * @param int $bulkSize The bulk size + * @param string $pk The primary key of the current table (default: id) + */ + public function doBulkInsert( + string $table, + array $data = [], + array $fixedValues = [], + $onDuplicateKey = null, + $callback = null, + $itemCallback = null, + int $bulkSize = 100, + string $pk = 'id' + ) { + /** @var Database $database */ + $database = $this->framework->createInstance(Database::class); + + if (!$database->tableExists($table) || empty($data)) { + return null; + } + + $fields = $database->getFieldNames($table, true); + StaticArrayUtil::removeValue($pk, $fields); // unset id + $fields = array_values($fields); + + $bulkSize = (int) $bulkSize; + + $query = ''; + $duplicateKey = ''; + $startQuery = sprintf('INSERT %s INTO %s (%s) VALUES ', self::ON_DUPLICATE_KEY_IGNORE === $onDuplicateKey ? 'IGNORE' : '', $table, implode(',', $fields)); + + if (self::ON_DUPLICATE_KEY_UPDATE === $onDuplicateKey) { + $duplicateKey = ' ON DUPLICATE KEY UPDATE '.implode( + ',', + array_map( + function ($val) { + // escape double quotes + return $val.' = VALUES('.$val.')'; + }, + $fields + ) + ); + } + + $i = 0; + + $columnWildcards = array_map( + function ($val) { + return '?'; + }, + $fields + ); + + foreach ($data as $key => $varData) { + if (0 === $i) { + $values = []; + $return = []; + $query = $startQuery; + } + + $columns = $columnWildcards; + + if ($varData instanceof Model) { + $varData = $varData->row(); + } + + foreach ($fields as $n => $strField) { + $varValue = isset($varData[$strField]) ? $varData[$strField] : 'DEFAULT'; + + if (\in_array($strField, array_keys($fixedValues))) { + $varValue = $fixedValues[$strField]; + } + + // replace SQL Keyword DEFAULT within wildcards ? + if ('DEFAULT' === $varValue) { + $columns[$n] = 'DEFAULT'; + + continue; + } + + $return[$i][$strField] = $varValue; + } + + // manipulate the item + if (\is_callable($itemCallback)) { + if (!isset($return[$i])) { + continue; + } + $varCallback = \call_user_func_array($itemCallback, [$return[$i], $fields, $varData]); + + if (!\is_array($varCallback)) { + continue; + } + + foreach ($fields as $n => $strField) { + $varValue = isset($varCallback[$strField]) ? $varCallback[$strField] : 'DEFAULT'; + + // replace SQL Keyword DEFAULT within wildcards ? + if ('DEFAULT' === $varValue) { + $columns[$n] = 'DEFAULT'; + + continue; + } + + $columns[$n] = '?'; + $return[$i][$strField] = $varValue; + } + } + + // add values to insert array + $values = array_merge($values, array_values($return[$i])); + + $query .= '('.implode(',', $columns).'),'; + + ++$i; + + if ($bulkSize === $i) { + $query = rtrim($query, ','); + + if (self::ON_DUPLICATE_KEY_UPDATE === $onDuplicateKey) { + $query .= $duplicateKey; + } + + $database->prepare($query)->execute($values); + + if (\is_callable($callback)) { + \call_user_func_array($callback, [$return]); + } + + $query = ''; + + $i = 0; + } + } + + // remaining elements < $intBulkSize + if ($query) { + $query = rtrim($query, ','); + + if (self::ON_DUPLICATE_KEY_UPDATE === $onDuplicateKey) { + $query .= $duplicateKey; + } + + $database->prepare($query)->execute($values); + + if (\is_callable($callback)) { + \call_user_func_array($callback, [$return]); + } + } } } diff --git a/src/DataContainer/EntityImportSourceContainer.php b/src/DataContainer/EntityImportSourceContainer.php index 6ac0805..95eda0c 100644 --- a/src/DataContainer/EntityImportSourceContainer.php +++ b/src/DataContainer/EntityImportSourceContainer.php @@ -13,14 +13,14 @@ use Contao\Message; use Contao\Model; use Contao\System; +use Doctrine\DBAL\Connection; use HeimrichHannot\EntityImportBundle\Event\AddSourceFieldMappingPresetsEvent; use HeimrichHannot\EntityImportBundle\Source\AbstractFileSource; use HeimrichHannot\EntityImportBundle\Source\CSVFileSource; use HeimrichHannot\EntityImportBundle\Source\RSSFileSource; use HeimrichHannot\EntityImportBundle\Source\SourceFactory; use HeimrichHannot\EntityImportBundle\Util\EntityImportUtil; -use HeimrichHannot\UtilsBundle\Database\DatabaseUtil; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class EntityImportSourceContainer @@ -59,20 +59,20 @@ class EntityImportSourceContainer protected $database; protected $cache; - protected ModelUtil $modelUtil; - protected SourceFactory $sourceFactory; - protected EntityImportUtil $util; + protected SourceFactory $sourceFactory; + protected EntityImportUtil $util; protected EventDispatcherInterface $eventDispatcher; - protected DatabaseUtil $databaseUtil; + protected Utils $utils; + protected Connection $connection; - public function __construct(SourceFactory $sourceFactory, ModelUtil $modelUtil, EntityImportUtil $util, EventDispatcherInterface $eventDispatcher, DatabaseUtil $databaseUtil) + public function __construct(SourceFactory $sourceFactory, EntityImportUtil $util, EventDispatcherInterface $eventDispatcher, Connection $connection, Utils $utils) { $this->activeBundles = System::getContainer()->getParameter('kernel.bundles'); $this->sourceFactory = $sourceFactory; - $this->modelUtil = $modelUtil; $this->util = $util; $this->eventDispatcher = $eventDispatcher; - $this->databaseUtil = $databaseUtil; + $this->utils = $utils; + $this->connection = $connection; } public function setPreset(?DataContainer $dc) @@ -83,15 +83,15 @@ public function setPreset(?DataContainer $dc) $dca = &$GLOBALS['TL_DCA']['tl_entity_import_source']; - $this->databaseUtil->update('tl_entity_import_source', [ + $this->connection->update('tl_entity_import_source', [ 'fieldMappingPresets' => '', 'fieldMapping' => serialize($dca['fields']['fieldMappingPresets']['eval']['presets'][$preset]), - ], 'tl_entity_import_source.id='.$dc->id); + ], ['tl_entity_import_source.id='.$dc->id]); } public function initPalette(?DataContainer $dc) { - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk($dc->table, $dc->id))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk($dc->table, $dc->id))) { return; } @@ -193,7 +193,7 @@ public function initPalette(?DataContainer $dc) public function onLoadFileContent(?string $value, ?DataContainer $dc) { - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $dc->id))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $dc->id))) { return ''; } @@ -240,7 +240,7 @@ public function getFileContent(Model $sourceModel) public function getAllTargetTables(?DataContainer $dc): array { - if (null === ($source = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $dc->id))) { + if (null === ($source = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $dc->id))) { return []; } diff --git a/src/DependencyInjection/HeimrichHannotEntityImportExtension.php b/src/DependencyInjection/HeimrichHannotEntityImportExtension.php index ad3f6a6..a6d46bb 100644 --- a/src/DependencyInjection/HeimrichHannotEntityImportExtension.php +++ b/src/DependencyInjection/HeimrichHannotEntityImportExtension.php @@ -23,7 +23,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter(Configuration::ROOT_ID, $this->processConfiguration($configuration, $configs)); } - public function getAlias() + public function getAlias(): string { return Configuration::ROOT_ID; } diff --git a/src/EventListener/Contao/SqlGetFromDcaEventListener.php b/src/EventListener/Contao/SqlGetFromDcaEventListener.php index 68a6529..7460ae2 100644 --- a/src/EventListener/Contao/SqlGetFromDcaEventListener.php +++ b/src/EventListener/Contao/SqlGetFromDcaEventListener.php @@ -10,20 +10,17 @@ use Contao\CoreBundle\Framework\ContaoFramework; use Contao\Database; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; use HeimrichHannot\UtilsBundle\Util\Utils; class SqlGetFromDcaEventListener { protected Utils $utils; - protected ModelUtil $modelUtil; protected ContaoFramework $framework; - public function __construct(ContaoFramework $framework, Utils $utils, ModelUtil $modelUtil) + public function __construct(ContaoFramework $framework, Utils $utils) { $this->framework = $framework; $this->utils = $utils; - $this->modelUtil = $modelUtil; } public function __invoke(array $sqlDcaData) @@ -44,7 +41,7 @@ public function __invoke(array $sqlDcaData) } // add cache fields to tl_entity_import_cache - if (null === ($importers = $this->modelUtil->findModelInstancesBy('tl_entity_import_config', [ + if (null === ($importers = $this->utils->model()->findModelInstancesBy('tl_entity_import_config', [ 'tl_entity_import_config.useCacheForQuickImporters=?', ], [ true, diff --git a/src/EventListener/LoadProgressListener.php b/src/EventListener/LoadProgressListener.php index 4b158ab..688b3b2 100644 --- a/src/EventListener/LoadProgressListener.php +++ b/src/EventListener/LoadProgressListener.php @@ -12,8 +12,7 @@ use HeimrichHannot\EntityImportBundle\Importer\ImporterInterface; use HeimrichHannot\ProgressBarWidgetBundle\Event\LoadProgressEvent; use HeimrichHannot\ProgressBarWidgetBundle\Widget\ProgressBar; -use HeimrichHannot\RequestBundle\Component\HttpFoundation\Request; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Terminal42\ServiceAnnotationBundle\Annotation\ServiceTag; /** @@ -21,28 +20,26 @@ */ class LoadProgressListener { - protected Request $request; - protected ModelUtil $modelUtil; + protected Utils $utils; - public function __construct(ModelUtil $modelUtil, Request $request) + public function __construct(Utils $utils) { - $this->request = $request; - $this->modelUtil = $modelUtil; + $this->utils = $utils; } public function __invoke(LoadProgressEvent $event) { if ('tl_entity_import_quick_config' === $event->getTable()) { - if (null === ($quickImporter = $this->modelUtil->findModelInstanceByPk('tl_entity_import_quick_config', $event->getId())) || + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $event->getId())) || !$quickImporter->importerConfig) { return; } - if (null === ($importConfig = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { + if (null === ($importConfig = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $quickImporter->importerConfig))) { return; } } else { - if (null === ($importConfig = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $event->getId()))) { + if (null === ($importConfig = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $event->getId()))) { return; } } diff --git a/src/HeimrichHannotContaoEntityImportBundle.php b/src/HeimrichHannotContaoEntityImportBundle.php index 45a3c86..7b45d87 100644 --- a/src/HeimrichHannotContaoEntityImportBundle.php +++ b/src/HeimrichHannotContaoEntityImportBundle.php @@ -9,11 +9,12 @@ namespace HeimrichHannot\EntityImportBundle; use HeimrichHannot\EntityImportBundle\DependencyInjection\HeimrichHannotEntityImportExtension; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\HttpKernel\Bundle\Bundle; class HeimrichHannotContaoEntityImportBundle extends Bundle { - public function getContainerExtension() + public function getContainerExtension(): ?ExtensionInterface { return new HeimrichHannotEntityImportExtension(); } diff --git a/src/Importer/Importer.php b/src/Importer/Importer.php index 0c6beae..244aad9 100644 --- a/src/Importer/Importer.php +++ b/src/Importer/Importer.php @@ -18,6 +18,7 @@ use Contao\Folder; use Contao\Message; use Contao\Model; +use Contao\StringUtil; use Contao\System; use Contao\Validator; use HeimrichHannot\EntityImportBundle\DataContainer\EntityImportConfigContainer; @@ -147,7 +148,7 @@ public function getMappedItems(array $options = []): array $mappedItems = []; - $mapping = \Contao\StringUtil::deserialize($this->configModel->fieldMapping, true); + $mapping = StringUtil::deserialize($this->configModel->fieldMapping, true); $mapping = $this->adjustMappingForDcMultilingual($mapping); $mapping = $this->adjustMappingForChangeLanguage($mapping); @@ -405,7 +406,7 @@ protected function executeImport(array $items): array $mode = $this->configModel->importMode; - $mapping = \Contao\StringUtil::deserialize($this->configModel->fieldMapping, true); + $mapping = StringUtil::deserialize($this->configModel->fieldMapping, true); $mapping = $this->adjustMappingForDcMultilingual($mapping); $mapping = $this->adjustMappingForChangeLanguage($mapping); @@ -413,7 +414,7 @@ protected function executeImport(array $items): array $dbItemMapping = []; if ('merge' === $mode) { - $mergeIdentifiers = \Contao\StringUtil::deserialize($this->configModel->mergeIdentifierFields, true); + $mergeIdentifiers = StringUtil::deserialize($this->configModel->mergeIdentifierFields, true); if (empty(array_filter($mergeIdentifiers))) { throw new Exception($GLOBALS['TL_LANG']['tl_entity_import_config']['error']['noIdentifierFields']); @@ -634,7 +635,7 @@ protected function updateMappingItemForSkippedFields(array &$mappingItem): void return; } - $skipFields = \Contao\StringUtil::deserialize($this->configModel->skipFieldsOnMerge, true); + $skipFields = StringUtil::deserialize($this->configModel->skipFieldsOnMerge, true); foreach ($skipFields as $skipField) { if (!\array_key_exists($skipField, $mappingItem)) { @@ -782,7 +783,7 @@ protected function deleteAfterImport(array $mappedItems) switch ($this->configModel->deletionMode) { case EntityImportConfigContainer::DELETION_MODE_MIRROR: - $deletionIdentifiers = \Contao\StringUtil::deserialize($this->configModel->deletionIdentifierFields, true); + $deletionIdentifiers = StringUtil::deserialize($this->configModel->deletionIdentifierFields, true); if (empty($deletionIdentifiers)) { throw new Exception($GLOBALS['TL_LANG']['tl_entity_import_config']['error']['noIdentifierFields']); @@ -894,7 +895,7 @@ protected function applyFieldFileMapping($record, $item): array { $set = []; $slugGenerator = new SlugGenerator(); - $fileMapping = \Contao\StringUtil::deserialize($this->configModel->fileFieldMapping, true); + $fileMapping = StringUtil::deserialize($this->configModel->fileFieldMapping, true); foreach ($fileMapping as $mapping) { if (($record->{$mapping['targetField']} ?? NULL) && $mapping['skipIfExisting']) { diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 42b5065..0ddc616 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -10,3 +10,7 @@ services: HeimrichHannot\EntityImportBundle\Importer\ImporterFactory: ~ HeimrichHannot\EntityImportBundle\Source\SourceFactory: ~ HeimrichHannot\EntityImportBundle\Util\EntityImportUtil: ~ + + HeimrichHannot\EntityImportBundle\Command\ExecuteImportCommand: + arguments: + $projectDir: '%kernel.project_dir%' diff --git a/src/Source/AbstractFileSource.php b/src/Source/AbstractFileSource.php index b94a7fd..a5484a5 100644 --- a/src/Source/AbstractFileSource.php +++ b/src/Source/AbstractFileSource.php @@ -9,30 +9,29 @@ namespace HeimrichHannot\EntityImportBundle\Source; use Ausi\SlugGenerator\SlugGenerator; +use Contao\CoreBundle\InsertTag\InsertTagParser; +use Contao\StringUtil; +use Contao\System; use HeimrichHannot\EntityImportBundle\DataContainer\EntityImportSourceContainer; use HeimrichHannot\EntityImportBundle\Event\AfterFileSourceGetContentEvent; use HeimrichHannot\EntityImportBundle\Event\BeforeAuthenticationEvent; -use HeimrichHannot\UtilsBundle\Container\ContainerUtil; -use HeimrichHannot\UtilsBundle\File\FileUtil; -use HeimrichHannot\UtilsBundle\String\StringUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; abstract class AbstractFileSource extends AbstractSource { - protected FileUtil $fileUtil; - protected StringUtil $stringUtil; protected EventDispatcherInterface $eventDispatcher; - protected ContainerUtil $containerUtil; + protected Utils $utils; + protected InsertTagParser $insertTagParser; /** * AbstractFileSource constructor. */ - public function __construct(EventDispatcherInterface $eventDispatcher, FileUtil $fileUtil, StringUtil $stringUtil, ContainerUtil $containerUtil) + public function __construct(EventDispatcherInterface $eventDispatcher, Utils $utils, InsertTagParser $insertTagParser) { - $this->fileUtil = $fileUtil; - $this->stringUtil = $stringUtil; - $this->containerUtil = $containerUtil; $this->eventDispatcher = $eventDispatcher; + $this->utils = $utils; + $this->insertTagParser = $insertTagParser; parent::__construct(); } @@ -48,11 +47,11 @@ public function getLinesFromFile(int $limit, bool $cache = false): string public function getFileContent(bool $cache = false): string { $content = ''; - $projectDir = $this->containerUtil->getProjectDir(); + $projectDir = System::getContainer()->getParameter('kernel.project_dir'); switch ($this->sourceModel->retrievalType) { case EntityImportSourceContainer::RETRIEVAL_TYPE_CONTAO_FILE_SYSTEM: - $path = $projectDir.'/'.$this->fileUtil->getPathFromUuid($this->sourceModel->fileSRC); + $path = $projectDir.'/'.$this->utils->file()->getPathFromUuid($this->sourceModel->fileSRC); if (file_exists($path)) { $content = file_get_contents($path); @@ -64,7 +63,7 @@ public function getFileContent(bool $cache = false): string $auth = []; if (null !== $this->sourceModel->httpAuth) { - $httpAuth = \Contao\StringUtil::deserialize($this->sourceModel->httpAuth, true); + $httpAuth = StringUtil::deserialize($this->sourceModel->httpAuth, true); $auth = ['auth' => [$httpAuth['username'], $httpAuth['password']]]; } diff --git a/src/Source/AbstractSource.php b/src/Source/AbstractSource.php index 16b93b3..a1bcc5a 100644 --- a/src/Source/AbstractSource.php +++ b/src/Source/AbstractSource.php @@ -10,6 +10,7 @@ use Contao\Environment; use Contao\Model; +use Contao\StringUtil; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use Symfony\Component\Cache\Adapter\FilesystemAdapter; @@ -103,7 +104,7 @@ protected function getContentFromUrl(string $method, string $url, array $auth = $client = new Client(); try { - $response = $client->request($method, \Contao\StringUtil::decodeEntities($url), $auth); + $response = $client->request($method, StringUtil::decodeEntities($url), $auth); } catch (RequestException $e) { return [ 'statusCode' => $e->getResponse()->getStatusCode(), diff --git a/src/Source/CSVFileSource.php b/src/Source/CSVFileSource.php index 95c63f0..471edab 100644 --- a/src/Source/CSVFileSource.php +++ b/src/Source/CSVFileSource.php @@ -8,6 +8,7 @@ namespace HeimrichHannot\EntityImportBundle\Source; +use Contao\File; use Contao\Message; use HeimrichHannot\EntityImportBundle\Util\CsvReader; use HeimrichHannot\EntityImportBundle\Event\AfterCsvFileSourceGetRowEvent; @@ -18,9 +19,9 @@ public function getMappedData(array $options = []): array { $data = []; $settings = $this->getCsvSettings(); - $file = $this->fileUtil->getFileFromUuid($this->sourceModel->fileSRC); + $file = new File($this->utils->file()->getPathFromUuid($this->sourceModel->fileSRC)); - if (null === $file || !$file->exists()) { + if (!$file->exists()) { return []; } diff --git a/src/Source/DatabaseSource.php b/src/Source/DatabaseSource.php index 486f707..6fd8464 100644 --- a/src/Source/DatabaseSource.php +++ b/src/Source/DatabaseSource.php @@ -8,30 +8,18 @@ namespace HeimrichHannot\EntityImportBundle\Source; +use Contao\Controller; use Contao\Database; +use Contao\DcaLoader; +use Contao\StringUtil; use HeimrichHannot\UtilsBundle\Dca\DcaUtil; class DatabaseSource extends AbstractSource { - /** - * @var DcaUtil - */ - private $dcaUtil; - - /** - * AbstractFileSource constructor. - */ - public function __construct(DcaUtil $dcaUtil) - { - $this->dcaUtil = $dcaUtil; - - parent::__construct(); - } - public function getMappedData(array $options = []): array { $sourceModel = $this->sourceModel; - $mapping = \Contao\StringUtil::deserialize($this->sourceModel->fieldMapping, true); + $mapping = StringUtil::deserialize($this->sourceModel->fieldMapping, true); $mapping = $this->adjustMappingForDcMultilingual($mapping); $mapping = $this->adjustMappingForChangeLanguage($mapping); @@ -68,7 +56,8 @@ protected function adjustMappingForDcMultilingual(array $mapping) 'skip' => true, ]; - $this->dcaUtil->loadDc($table); + $loader = new DcaLoader($table); + $loader->load(); $dca = $GLOBALS['TL_DCA'][$table]; diff --git a/src/Source/JSONFileSource.php b/src/Source/JSONFileSource.php index adb7f89..fe353eb 100644 --- a/src/Source/JSONFileSource.php +++ b/src/Source/JSONFileSource.php @@ -53,7 +53,7 @@ protected function getMappedItemData(?array $element, array $mapping): array foreach ($mapping as $mappingElement) { if ('static_value' === $mappingElement['valueType']) { - $result[$mappingElement['name']] = $this->stringUtil->replaceInsertTags($mappingElement['staticValue']); + $result[$mappingElement['name']] = $this->insertTagParser->replace($mappingElement['staticValue']); } elseif ('source_value' === $mappingElement['valueType']) { $result[$mappingElement['name']] = $this->getValue($element, $mappingElement); } diff --git a/src/Source/RSSFileSource.php b/src/Source/RSSFileSource.php index c93ebc6..2bad6d7 100644 --- a/src/Source/RSSFileSource.php +++ b/src/Source/RSSFileSource.php @@ -8,6 +8,9 @@ namespace HeimrichHannot\EntityImportBundle\Source; +use Contao\StringUtil; +use LitEmoji\LitEmoji; + class RSSFileSource extends AbstractFileSource { public function getPostFieldsAsOptions() @@ -73,7 +76,7 @@ public function getChannelItems() public function getMappedData(array $options = []): array { $sourceModel = $this->sourceModel; - $mapping = \Contao\StringUtil::deserialize($sourceModel->fieldMapping, true); + $mapping = StringUtil::deserialize($sourceModel->fieldMapping, true); $data = []; @@ -94,7 +97,7 @@ protected function getMappedItemData(?array $element, array $mapping): array foreach ($mapping as $mappingElement) { if ('static_value' === $mappingElement['valueType']) { - $result[$mappingElement['name']] = $this->stringUtil->replaceInsertTags($mappingElement['staticValue']); + $result[$mappingElement['name']] = $this->insertTagParser->replace($mappingElement['staticValue']); } elseif ('source_value' === $mappingElement['valueType']) { $value = $element[$mappingElement['sourceValue']]; @@ -110,7 +113,7 @@ protected function getMappedItemData(?array $element, array $mapping): array break; case 'encoded': - $result[$mappingElement['name']] = $this->stringUtil->replaceUnicodeEmojisByHtml($value); + $result[$mappingElement['name']] = LitEmoji::encodeHtml($value); break; diff --git a/src/Source/SourceFactory.php b/src/Source/SourceFactory.php index 10815d3..b0edbdc 100644 --- a/src/Source/SourceFactory.php +++ b/src/Source/SourceFactory.php @@ -8,43 +8,35 @@ namespace HeimrichHannot\EntityImportBundle\Source; +use Contao\CoreBundle\InsertTag\InsertTagParser; +use Contao\StringUtil; use HeimrichHannot\EntityImportBundle\DataContainer\EntityImportSourceContainer; use HeimrichHannot\EntityImportBundle\Event\SourceFactoryCreateSourceEvent; -use HeimrichHannot\UtilsBundle\Container\ContainerUtil; -use HeimrichHannot\UtilsBundle\Dca\DcaUtil; -use HeimrichHannot\UtilsBundle\File\FileUtil; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; -use HeimrichHannot\UtilsBundle\String\StringUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class SourceFactory { protected ContainerInterface $container; - protected ModelUtil $modelUtil; - protected FileUtil $fileUtil; protected EventDispatcherInterface $eventDispatcher; - protected StringUtil $stringUtil; - protected ContainerUtil $containerUtil; - protected DcaUtil $dcaUtil; + protected Utils $utils; + protected InsertTagParser $insertTagParser; /** * SourceFactory constructor. */ - public function __construct(ContainerInterface $container, ModelUtil $modelUtil, FileUtil $fileUtil, EventDispatcherInterface $eventDispatcher, StringUtil $stringUtil, ContainerUtil $containerUtil, DcaUtil $dcaUtil) + public function __construct(ContainerInterface $container, EventDispatcherInterface $eventDispatcher, Utils $utils, InsertTagParser $insertTagParser) { - $this->modelUtil = $modelUtil; - $this->fileUtil = $fileUtil; $this->eventDispatcher = $eventDispatcher; - $this->stringUtil = $stringUtil; - $this->containerUtil = $containerUtil; - $this->dcaUtil = $dcaUtil; $this->container = $container; + $this->utils = $utils; + $this->insertTagParser = $insertTagParser; } public function createInstance(int $sourceModel): ?SourceInterface { - if (null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $sourceModel))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $sourceModel))) { return null; } @@ -52,29 +44,29 @@ public function createInstance(int $sourceModel): ?SourceInterface switch ($sourceModel->type) { case EntityImportSourceContainer::TYPE_DATABASE: - $source = new DatabaseSource($this->dcaUtil); + $source = new DatabaseSource(); break; case EntityImportSourceContainer::TYPE_FILE: switch ($sourceModel->fileType) { case EntityImportSourceContainer::FILETYPE_JSON: - $source = new JSONFileSource($this->eventDispatcher, $this->fileUtil, $this->stringUtil, $this->containerUtil); + $source = new JSONFileSource($this->eventDispatcher, $this->utils, $this->insertTagParser); break; case EntityImportSourceContainer::FILETYPE_XML: - $source = new XmlFileSource($this->eventDispatcher, $this->fileUtil, $this->stringUtil, $this->containerUtil); + $source = new XmlFileSource($this->eventDispatcher, $this->utils, $this->insertTagParser); break; case EntityImportSourceContainer::FILETYPE_CSV: - $source = new CSVFileSource($this->eventDispatcher, $this->fileUtil, $this->stringUtil, $this->containerUtil); + $source = new CSVFileSource($this->eventDispatcher, $this->utils, $this->insertTagParser); break; case EntityImportSourceContainer::FILETYPE_RSS: - $source = new RSSFileSource($this->eventDispatcher, $this->fileUtil, $this->stringUtil, $this->containerUtil); + $source = new RSSFileSource($this->eventDispatcher, $this->utils, $this->insertTagParser); break; } @@ -93,7 +85,7 @@ public function createInstance(int $sourceModel): ?SourceInterface throw new \Exception('No file source class found for file type '.$sourceModel->fileType); } - $source->setFieldMapping(\Contao\StringUtil::deserialize($sourceModel->fieldMapping, true)); + $source->setFieldMapping(StringUtil::deserialize($sourceModel->fieldMapping, true)); $source->setSourceModel($sourceModel); $source->setContainer($this->container); diff --git a/src/Source/XmlFileSource.php b/src/Source/XmlFileSource.php index 56d3d86..1fc38ff 100644 --- a/src/Source/XmlFileSource.php +++ b/src/Source/XmlFileSource.php @@ -55,7 +55,7 @@ protected function getMappedItemData(?array $element, array $mapping): array foreach ($mapping as $mappingElement) { if ('static_value' === $mappingElement['valueType']) { - $result[$mappingElement['name']] = $this->stringUtil->replaceInsertTags($mappingElement['staticValue']); + $result[$mappingElement['name']] = $this->insertTagParser->replace($mappingElement['staticValue']); } elseif ('source_value' === $mappingElement['valueType']) { $result[$mappingElement['name']] = $this->getValue($element, $mappingElement); } diff --git a/src/Util/EntityImportUtil.php b/src/Util/EntityImportUtil.php index 11574cc..d47be55 100644 --- a/src/Util/EntityImportUtil.php +++ b/src/Util/EntityImportUtil.php @@ -8,6 +8,9 @@ namespace HeimrichHannot\EntityImportBundle\Util; +use Contao\DcaLoader; +use Contao\System; + class EntityImportUtil { public function transformFieldMappingSourceValueToSelect($options) @@ -20,4 +23,13 @@ public function transformFieldMappingSourceValueToSelect($options) $dca['eval']['mandatory'] = true; $dca['eval']['chosen'] = true; } + + public function getLocalizedFieldName(string $strField, string $strTable): ?string + { + $loader = new DcaLoader($strTable); + $loader->load(); + System::loadLanguageFile($strTable); + + return $GLOBALS['TL_DCA'][$strTable]['fields'][$strField]['label'][0] ?: $strField; + } } From 803dee259cf4471ccf87f007eade6ed94a27ab31 Mon Sep 17 00:00:00 2001 From: dpa Date: Tue, 9 Sep 2025 09:16:40 +0200 Subject: [PATCH 2/6] feat: refactorings for contao 5 --- .../services.yml => config/services.yaml | 2 +- .../contao => contao}/config/config.php | 5 - .../dca/tl_entity_import_cache.php | 2 +- .../dca/tl_entity_import_config.php | 21 +- .../dca/tl_entity_import_quick_config.php | 21 +- .../dca/tl_entity_import_source.php | 32 +- .../contao => contao}/dca/tl_module.php | 0 .../languages/de/default.php | 0 .../languages/de/modules.php | 0 .../languages/de/tl_entity_import_config.php | 0 .../de/tl_entity_import_quick_config.php | 0 .../languages/de/tl_entity_import_source.php | 0 .../languages/en/default.php | 0 .../languages/en/modules.php | 0 .../languages/en/tl_entity_import_config.php | 0 .../en/tl_entity_import_quick_config.php | 0 .../languages/en/tl_entity_import_source.php | 0 .../css/contao-entity-import-bundle-be.css | 2 +- .../contao-entity-import-bundle-be.min.css | 1 + .../js/contao-entity-import-bundle-be.js | 2 - .../js/contao-entity-import-bundle-be.min.js | 1 + src/ContaoManager/Plugin.php | 2 +- .../EntityImportConfigContainer.php | 8 +- .../EntityImportQuickConfigContainer.php | 2 +- .../EntityImportSourceContainer.php | 20 +- .../Contao/LoadDataContainerListener.php | 31 ++ ...HeimrichHannotContaoEntityImportBundle.php | 5 + src/Importer/Importer.php | 280 ++++++------ src/Importer/ImporterFactory.php | 57 +-- .../assets/contao-entity-import-bundle-be.css | 1 - .../assets/contao-entity-import-bundle-be.js | 1 - src/Resources/public/assets/entrypoints.json | 12 - src/Resources/public/assets/manifest.json | 4 - src/Source/AbstractSource.php | 3 +- src/Source/DatabaseSource.php | 22 +- src/Source/SourceFactory.php | 13 +- src/Util/EntityImportUtil.php | 415 ++++++++++++++++++ 37 files changed, 693 insertions(+), 272 deletions(-) rename src/Resources/config/services.yml => config/services.yaml (85%) rename {src/Resources/contao => contao}/config/config.php (87%) rename {src/Resources/contao => contao}/dca/tl_entity_import_cache.php (92%) rename {src/Resources/contao => contao}/dca/tl_entity_import_config.php (98%) rename {src/Resources/contao => contao}/dca/tl_entity_import_quick_config.php (91%) rename {src/Resources/contao => contao}/dca/tl_entity_import_source.php (95%) rename {src/Resources/contao => contao}/dca/tl_module.php (100%) rename {src/Resources/contao => contao}/languages/de/default.php (100%) rename {src/Resources/contao => contao}/languages/de/modules.php (100%) rename {src/Resources/contao => contao}/languages/de/tl_entity_import_config.php (100%) rename {src/Resources/contao => contao}/languages/de/tl_entity_import_quick_config.php (100%) rename {src/Resources/contao => contao}/languages/de/tl_entity_import_source.php (100%) rename {src/Resources/contao => contao}/languages/en/default.php (100%) rename {src/Resources/contao => contao}/languages/en/modules.php (100%) rename {src/Resources/contao => contao}/languages/en/tl_entity_import_config.php (100%) rename {src/Resources/contao => contao}/languages/en/tl_entity_import_quick_config.php (100%) rename {src/Resources/contao => contao}/languages/en/tl_entity_import_source.php (100%) rename src/Resources/assets/scss/contao-entity-import-bundle-be.scss => public/css/contao-entity-import-bundle-be.css (73%) create mode 100644 public/css/contao-entity-import-bundle-be.min.css rename {src/Resources/assets => public}/js/contao-entity-import-bundle-be.js (95%) create mode 100644 public/js/contao-entity-import-bundle-be.min.js create mode 100644 src/EventListener/Contao/LoadDataContainerListener.php delete mode 100644 src/Resources/public/assets/contao-entity-import-bundle-be.css delete mode 100644 src/Resources/public/assets/contao-entity-import-bundle-be.js delete mode 100644 src/Resources/public/assets/entrypoints.json delete mode 100644 src/Resources/public/assets/manifest.json diff --git a/src/Resources/config/services.yml b/config/services.yaml similarity index 85% rename from src/Resources/config/services.yml rename to config/services.yaml index 0ddc616..b747ebf 100644 --- a/src/Resources/config/services.yml +++ b/config/services.yaml @@ -5,7 +5,7 @@ services: public: true HeimrichHannot\EntityImportBundle\: - resource: '../../{Command,Controller,DataContainer,EventListener}/*' + resource: '../src/{Command,Controller,DataContainer,EventListener}/*' HeimrichHannot\EntityImportBundle\Importer\ImporterFactory: ~ HeimrichHannot\EntityImportBundle\Source\SourceFactory: ~ diff --git a/src/Resources/contao/config/config.php b/contao/config/config.php similarity index 87% rename from src/Resources/contao/config/config.php rename to contao/config/config.php index c822d06..ec693b7 100644 --- a/src/Resources/contao/config/config.php +++ b/contao/config/config.php @@ -44,8 +44,3 @@ $GLOBALS['TL_CRON']['daily'][] = [HeimrichHannot\EntityImportBundle\Controller\PoorManCronController::class, 'runDaily']; $GLOBALS['TL_CRON']['weekly'][] = [HeimrichHannot\EntityImportBundle\Controller\PoorManCronController::class, 'runWeekly']; $GLOBALS['TL_CRON']['monthly'][] = [HeimrichHannot\EntityImportBundle\Controller\PoorManCronController::class, 'runMonthly']; - -if ('BE' === TL_MODE) { - $GLOBALS['TL_CSS']['be_entityimportbundle'] = 'bundles/heimrichhannotcontaoentityimport/assets/contao-entity-import-bundle-be.css|static'; - $GLOBALS['TL_JAVASCRIPT']['be_entityimportbundle'] = 'bundles/heimrichhannotcontaoentityimport/assets/contao-entity-import-bundle-be.js|static'; -} diff --git a/src/Resources/contao/dca/tl_entity_import_cache.php b/contao/dca/tl_entity_import_cache.php similarity index 92% rename from src/Resources/contao/dca/tl_entity_import_cache.php rename to contao/dca/tl_entity_import_cache.php index 595a1c9..f7421be 100644 --- a/src/Resources/contao/dca/tl_entity_import_cache.php +++ b/contao/dca/tl_entity_import_cache.php @@ -8,7 +8,7 @@ $GLOBALS['TL_DCA']['tl_entity_import_cache'] = [ 'config' => [ - 'dataContainer' => 'Table', + 'dataContainer' => \Contao\DC_Table::class, 'sql' => [ 'keys' => [ 'id' => 'primary', diff --git a/src/Resources/contao/dca/tl_entity_import_config.php b/contao/dca/tl_entity_import_config.php similarity index 98% rename from src/Resources/contao/dca/tl_entity_import_config.php rename to contao/dca/tl_entity_import_config.php index 2089678..1d2f1a4 100644 --- a/src/Resources/contao/dca/tl_entity_import_config.php +++ b/contao/dca/tl_entity_import_config.php @@ -6,19 +6,17 @@ * @license LGPL-3.0-or-later */ +\HeimrichHannot\UtilsBundle\Dca\DateAddedField::register('tl_entity_import_config'); + $GLOBALS['TL_DCA']['tl_entity_import_config'] = [ 'config' => [ - 'dataContainer' => 'Table', + 'dataContainer' => \Contao\DC_Table::class, 'enableVersioning' => true, 'ptable' => 'tl_entity_import_source', 'onload_callback' => [[\HeimrichHannot\EntityImportBundle\DataContainer\EntityImportConfigContainer::class, 'initPalette']], 'onsubmit_callback' => [ - ['huh.utils.dca', 'setDateAdded'], [\HeimrichHannot\EntityImportBundle\DataContainer\EntityImportConfigContainer::class, 'setPreset'], ], - 'oncopy_callback' => [ - ['huh.utils.dca', 'setDateAddedOnCopy'], - ], 'sql' => [ 'keys' => [ 'id' => 'primary', @@ -77,8 +75,8 @@ 'import' => [ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_config']['import'], 'href' => 'key=import', - 'icon' => 'store.svg', - 'attributes' => 'onclick="if (!confirm(\''.($GLOBALS['TL_LANG']['tl_entity_import_config']['importConfirm'] ?? null).'\')) return false; Backend.getScrollOffset();"', + 'icon' => 'theme_import.svg', + 'attributes' => 'data-turbo="false" onclick="if (!confirm(\''.($GLOBALS['TL_LANG']['tl_entity_import_config']['importConfirm'] ?? null).'\')) return false; Backend.getScrollOffset();"', ], ], ], @@ -110,13 +108,6 @@ 'sql' => "int(10) unsigned NOT NULL default '0'", 'relation' => ['type' => 'belongsTo', 'load' => 'eager'], ], - 'dateAdded' => [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['dateAdded'], - 'sorting' => true, - 'flag' => 6, - 'eval' => ['rgxp' => 'datim', 'doNotCopy' => true], - 'sql' => "int(10) unsigned NOT NULL default '0'", - ], 'tstamp' => [ 'sql' => "int(10) unsigned NOT NULL default '0'", ], @@ -549,7 +540,7 @@ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_config']['cronLanguage'], 'exclude' => true, 'inputType' => 'select', - 'options' => \Contao\System::getLanguages(), + 'options' => \Contao\System::getContainer()->get('contao.intl.locales')->getLanguages(), 'eval' => ['tl_class' => 'w50', 'mandatory' => true, 'includeBlankOption' => true, 'chosen' => true], 'sql' => "varchar(64) NOT NULL default ''", ], diff --git a/src/Resources/contao/dca/tl_entity_import_quick_config.php b/contao/dca/tl_entity_import_quick_config.php similarity index 91% rename from src/Resources/contao/dca/tl_entity_import_quick_config.php rename to contao/dca/tl_entity_import_quick_config.php index 34ae388..b9493dd 100644 --- a/src/Resources/contao/dca/tl_entity_import_quick_config.php +++ b/contao/dca/tl_entity_import_quick_config.php @@ -6,23 +6,21 @@ * @license LGPL-3.0-or-later */ -System::getContainer()->get('huh.utils.dca')->loadLanguageFile('tl_entity_import_source'); -System::getContainer()->get('huh.utils.dca')->loadLanguageFile('tl_entity_import_config'); +\Contao\System::loadLanguageFile('tl_entity_import_source'); +\Contao\System::loadLanguageFile('tl_entity_import_config'); + +\HeimrichHannot\UtilsBundle\Dca\DateAddedField::register('tl_entity_import_quick_config'); $GLOBALS['TL_DCA']['tl_entity_import_quick_config'] = [ 'config' => [ - 'dataContainer' => 'Table', + 'dataContainer' => \Contao\DC_Table::class, 'enableVersioning' => true, 'onload_callback' => [ [\HeimrichHannot\EntityImportBundle\DataContainer\EntityImportQuickConfigContainer::class, 'modifyDca'], ], 'onsubmit_callback' => [ - ['huh.utils.dca', 'setDateAdded'], [\HeimrichHannot\EntityImportBundle\DataContainer\EntityImportQuickConfigContainer::class, 'cacheCsvRows'], ], - 'oncopy_callback' => [ - ['huh.utils.dca', 'setDateAddedOnCopy'], - ], 'sql' => [ 'keys' => [ 'id' => 'primary', @@ -79,7 +77,7 @@ 'import' => [ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_quick_config']['import'], 'href' => 'key=import', - 'icon' => 'store.svg', + 'icon' => 'theme_import.svg', 'attributes' => 'onclick="if (!confirm(\''.$GLOBALS['TL_LANG']['tl_entity_import_config']['importConfirm'].'\')) return false; Backend.getScrollOffset();"', ], ], @@ -95,13 +93,6 @@ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_quick_config']['tstamp'], 'sql' => "int(10) unsigned NOT NULL default '0'", ], - 'dateAdded' => [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['dateAdded'], - 'sorting' => true, - 'flag' => 6, - 'eval' => ['rgxp' => 'datim', 'doNotCopy' => true], - 'sql' => "int(10) unsigned NOT NULL default '0'", - ], 'title' => [ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_quick_config']['title'], 'exclude' => true, diff --git a/src/Resources/contao/dca/tl_entity_import_source.php b/contao/dca/tl_entity_import_source.php similarity index 95% rename from src/Resources/contao/dca/tl_entity_import_source.php rename to contao/dca/tl_entity_import_source.php index 8ba04cb..8982996 100644 --- a/src/Resources/contao/dca/tl_entity_import_source.php +++ b/contao/dca/tl_entity_import_source.php @@ -6,20 +6,21 @@ * @license LGPL-3.0-or-later */ +\HeimrichHannot\UtilsBundle\Dca\DateAddedField::register('tl_entity_import_source'); + +$connection = \Contao\System::getContainer()->get('doctrine.dbal.default_connection'); +$connectionParams = $connection->getParams(); + $GLOBALS['TL_DCA']['tl_entity_import_source'] = [ // Config 'config' => [ - 'dataContainer' => 'Table', + 'dataContainer' => \Contao\DC_Table::class, 'ctable' => ['tl_entity_import_config'], 'enableVersioning' => true, 'onload_callback' => [[\HeimrichHannot\EntityImportBundle\DataContainer\EntityImportSourceContainer::class, 'initPalette']], 'onsubmit_callback' => [ - ['huh.utils.dca', 'setDateAdded'], [\HeimrichHannot\EntityImportBundle\DataContainer\EntityImportSourceContainer::class, 'setPreset'], ], - 'oncopy_callback' => [ - ['huh.utils.dca', 'setDateAddedOnCopy'], - ], 'sql' => [ 'keys' => [ 'id' => 'primary', @@ -103,13 +104,6 @@ 'tstamp' => [ 'sql' => "int(10) unsigned NOT NULL default '0'", ], - 'dateAdded' => [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['dateAdded'], - 'sorting' => true, - 'flag' => 6, - 'eval' => ['rgxp' => 'datim', 'doNotCopy' => true], - 'sql' => "int(10) unsigned NOT NULL default '0'", - ], 'title' => [ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['title'], 'search' => true, @@ -132,8 +126,8 @@ 'exclude' => true, 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['dbDriver'], 'inputType' => 'select', - 'default' => version_compare(VERSION, '4.0', '<') ? \Config::get('dbDriver') : 'pdo_mysql', - 'options' => version_compare(VERSION, '4.0', '<') ? ['MySQLi', 'MySQL'] : ['pdo_mysql'], + 'default' => 'pdo_mysql', + 'options' => ['pdo_mysql'], 'eval' => ['mandatory' => true, 'tl_class' => 'w50'], 'sql' => "varchar(12) NOT NULL default ''", ], @@ -141,7 +135,7 @@ 'exclude' => true, 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['dbHost'], 'inputType' => 'text', - 'default' => \Config::get('dbHost'), + 'default' => $connectionParams['host'], 'eval' => ['mandatory' => true, 'maxlength' => 64, 'tl_class' => 'w50'], 'sql' => "varchar(64) NOT NULL default ''", ], @@ -149,7 +143,7 @@ 'exclude' => true, 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['dbUser'], 'inputType' => 'text', - 'default' => \Config::get('dbUser') ?: '', + 'default' => $connectionParams['user'] ?? '', 'eval' => ['mandatory' => true, 'maxlength' => 64, 'tl_class' => 'w50'], 'sql' => "varchar(64) NOT NULL default ''", ], @@ -164,7 +158,7 @@ 'exclude' => true, 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['dbDatabase'], 'inputType' => 'text', - 'default' => \Config::get('dbDatabase') ?: '', + 'default' => $connectionParams['dbname'] ?? '', 'eval' => ['mandatory' => true, 'maxlength' => 64, 'tl_class' => 'w50'], 'sql' => "varchar(64) NOT NULL default ''", ], @@ -181,7 +175,7 @@ 'exclude' => true, 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['dbCharset'], 'inputType' => 'text', - 'default' => \Config::get('dbCharset'), + 'default' => $connectionParams['charset'] ?? '', 'eval' => ['mandatory' => true, 'maxlength' => 32, 'tl_class' => 'w50'], 'sql' => "varchar(32) NOT NULL default ''", ], @@ -189,7 +183,7 @@ 'exclude' => true, 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_source']['dbPort'], 'inputType' => 'text', - 'default' => \Config::get('dbPort'), + 'default' => $connectionParams['port'] ?? 0, 'eval' => ['maxlength' => 5, 'tl_class' => 'w50', 'rgxp' => 'digit'], 'sql' => "int(5) unsigned NOT NULL default '0'", ], diff --git a/src/Resources/contao/dca/tl_module.php b/contao/dca/tl_module.php similarity index 100% rename from src/Resources/contao/dca/tl_module.php rename to contao/dca/tl_module.php diff --git a/src/Resources/contao/languages/de/default.php b/contao/languages/de/default.php similarity index 100% rename from src/Resources/contao/languages/de/default.php rename to contao/languages/de/default.php diff --git a/src/Resources/contao/languages/de/modules.php b/contao/languages/de/modules.php similarity index 100% rename from src/Resources/contao/languages/de/modules.php rename to contao/languages/de/modules.php diff --git a/src/Resources/contao/languages/de/tl_entity_import_config.php b/contao/languages/de/tl_entity_import_config.php similarity index 100% rename from src/Resources/contao/languages/de/tl_entity_import_config.php rename to contao/languages/de/tl_entity_import_config.php diff --git a/src/Resources/contao/languages/de/tl_entity_import_quick_config.php b/contao/languages/de/tl_entity_import_quick_config.php similarity index 100% rename from src/Resources/contao/languages/de/tl_entity_import_quick_config.php rename to contao/languages/de/tl_entity_import_quick_config.php diff --git a/src/Resources/contao/languages/de/tl_entity_import_source.php b/contao/languages/de/tl_entity_import_source.php similarity index 100% rename from src/Resources/contao/languages/de/tl_entity_import_source.php rename to contao/languages/de/tl_entity_import_source.php diff --git a/src/Resources/contao/languages/en/default.php b/contao/languages/en/default.php similarity index 100% rename from src/Resources/contao/languages/en/default.php rename to contao/languages/en/default.php diff --git a/src/Resources/contao/languages/en/modules.php b/contao/languages/en/modules.php similarity index 100% rename from src/Resources/contao/languages/en/modules.php rename to contao/languages/en/modules.php diff --git a/src/Resources/contao/languages/en/tl_entity_import_config.php b/contao/languages/en/tl_entity_import_config.php similarity index 100% rename from src/Resources/contao/languages/en/tl_entity_import_config.php rename to contao/languages/en/tl_entity_import_config.php diff --git a/src/Resources/contao/languages/en/tl_entity_import_quick_config.php b/contao/languages/en/tl_entity_import_quick_config.php similarity index 100% rename from src/Resources/contao/languages/en/tl_entity_import_quick_config.php rename to contao/languages/en/tl_entity_import_quick_config.php diff --git a/src/Resources/contao/languages/en/tl_entity_import_source.php b/contao/languages/en/tl_entity_import_source.php similarity index 100% rename from src/Resources/contao/languages/en/tl_entity_import_source.php rename to contao/languages/en/tl_entity_import_source.php diff --git a/src/Resources/assets/scss/contao-entity-import-bundle-be.scss b/public/css/contao-entity-import-bundle-be.css similarity index 73% rename from src/Resources/assets/scss/contao-entity-import-bundle-be.scss rename to public/css/contao-entity-import-bundle-be.css index 6f8b772..025a7cf 100644 --- a/src/Resources/assets/scss/contao-entity-import-bundle-be.scss +++ b/public/css/contao-entity-import-bundle-be.css @@ -1,4 +1,4 @@ -#tl_entity_import_source{ +#tl_entity_import_source { [name="fileContent"] + .ace_editor { height: 500px !important; } diff --git a/public/css/contao-entity-import-bundle-be.min.css b/public/css/contao-entity-import-bundle-be.min.css new file mode 100644 index 0000000..844e84b --- /dev/null +++ b/public/css/contao-entity-import-bundle-be.min.css @@ -0,0 +1 @@ +#tl_entity_import_source{[name="fileContent"]+.ace_editor{height:500px!important}} diff --git a/src/Resources/assets/js/contao-entity-import-bundle-be.js b/public/js/contao-entity-import-bundle-be.js similarity index 95% rename from src/Resources/assets/js/contao-entity-import-bundle-be.js rename to public/js/contao-entity-import-bundle-be.js index 2dc17eb..0fc4b27 100644 --- a/src/Resources/assets/js/contao-entity-import-bundle-be.js +++ b/public/js/contao-entity-import-bundle-be.js @@ -1,5 +1,3 @@ -import '../scss/contao-entity-import-bundle-be.scss'; - class ContaoEntityImportBundleBe { static init() { ContaoEntityImportBundleBe.removeWidthLimitForQuickImporters(); diff --git a/public/js/contao-entity-import-bundle-be.min.js b/public/js/contao-entity-import-bundle-be.min.js new file mode 100644 index 0000000..079c7b9 --- /dev/null +++ b/public/js/contao-entity-import-bundle-be.min.js @@ -0,0 +1 @@ +class ContaoEntityImportBundleBe{static init(){ContaoEntityImportBundleBe.removeWidthLimitForQuickImporters()}static removeWidthLimitForQuickImporters(){if(null===document.getElementById("tl_entity_import_quick_config")||null===document.querySelector(".list-widget"))return;document.querySelector("#header .inner").style["max-width"]="none",document.getElementById("container").style["max-width"]="none",document.getElementById("main").style.width="auto";let t=0;document.querySelectorAll(".tl_formbody_edit .widget").forEach(e=>{t++,null===e.querySelector(".list-widget")&&(e.style["max-width"]=e.classList.contains("long")?"1000px":"500px",t%2==1&&e.classList.add("clr"))})}}document.addEventListener("DOMContentLoaded",ContaoEntityImportBundleBe.init); diff --git a/src/ContaoManager/Plugin.php b/src/ContaoManager/Plugin.php index 9acaeeb..e10feba 100644 --- a/src/ContaoManager/Plugin.php +++ b/src/ContaoManager/Plugin.php @@ -30,6 +30,6 @@ public function getBundles(ParserInterface $parser) public function registerContainerConfiguration(LoaderInterface $loader, array $managerConfig) { - $loader->load('@HeimrichHannotContaoEntityImportBundle/Resources/config/services.yml'); + $loader->load('@HeimrichHannotContaoEntityImportBundle/config/services.yaml'); } } diff --git a/src/DataContainer/EntityImportConfigContainer.php b/src/DataContainer/EntityImportConfigContainer.php index 8ba4234..32b64cc 100644 --- a/src/DataContainer/EntityImportConfigContainer.php +++ b/src/DataContainer/EntityImportConfigContainer.php @@ -72,7 +72,7 @@ public function getDryRunOperation($row, $href, $label, $title, $icon, $attribut return ''; } - return '' . Image::getHtml($icon, $label) . ' '; + return '' . Image::getHtml($icon, $label) . ' '; } public function setPreset(?DataContainer $dc) @@ -86,7 +86,7 @@ public function setPreset(?DataContainer $dc) $this->connection->update('tl_entity_import_config', [ 'fieldMappingPresets' => '', 'fieldMapping' => serialize($dca['fields']['fieldMappingPresets']['eval']['presets'][$preset]), - ], ['tl_entity_import_config.id=' . $dc->id]); + ], ['tl_entity_import_config.id' => $dc->id]); } public function initPalette(?DataContainer $dc) @@ -153,6 +153,8 @@ public function getSourceFields(?DataContainer $dc): array } } + asort($options); + return $options; } @@ -178,6 +180,8 @@ public function getTargetFields(?DataContainer $dc): array $options[$field['name']] = $field['name'] . ' [' . $field['origtype'] . ']'; } + asort($options); + return $options; } diff --git a/src/DataContainer/EntityImportQuickConfigContainer.php b/src/DataContainer/EntityImportQuickConfigContainer.php index 5a98d75..74a9a9c 100644 --- a/src/DataContainer/EntityImportQuickConfigContainer.php +++ b/src/DataContainer/EntityImportQuickConfigContainer.php @@ -159,7 +159,7 @@ public function loadCsvRowsFromCache($config, $options = [], $context = null, $d public function cacheCsvRows(DataContainer $dc) { // cache might be invalid now -> delete tl_md_recipient - $this->connection->delete('tl_entity_import_cache', ['cache_ptable="tl_entity_import_quick_config" AND cache_pid=' . $dc->id]); + $this->connection->delete('tl_entity_import_cache', ['cache_ptable' => 'tl_entity_import_quick_config', 'cache_pid' => $dc->id]); // cache the rows if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || diff --git a/src/DataContainer/EntityImportSourceContainer.php b/src/DataContainer/EntityImportSourceContainer.php index 95eda0c..023ae82 100644 --- a/src/DataContainer/EntityImportSourceContainer.php +++ b/src/DataContainer/EntityImportSourceContainer.php @@ -8,7 +8,6 @@ namespace HeimrichHannot\EntityImportBundle\DataContainer; -use Contao\Database; use Contao\DataContainer; use Contao\Message; use Contao\Model; @@ -86,12 +85,12 @@ public function setPreset(?DataContainer $dc) $this->connection->update('tl_entity_import_source', [ 'fieldMappingPresets' => '', 'fieldMapping' => serialize($dca['fields']['fieldMappingPresets']['eval']['presets'][$preset]), - ], ['tl_entity_import_source.id='.$dc->id]); + ], ['tl_entity_import_source.id' => $dc->id]); } public function initPalette(?DataContainer $dc) { - if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk($dc->table, $dc->id))) { + if (null === ($sourceModel = $this->utils->model()->findModelInstanceByPk($dc->table, $dc->id ?: 0))) { return; } @@ -105,7 +104,11 @@ public function initPalette(?DataContainer $dc) $dca['palettes'][static::TYPE_DATABASE] = str_replace('fieldMapping', '', $dca['palettes'][static::TYPE_DATABASE]); } else { try { - $options = array_values(Database::getInstance($sourceModel->row())->getFieldNames($sourceModel->dbSourceTable, true)); + $connection = $this->util->getDbalConnectionBySource($sourceModel->row()); + $schemaManager = $connection->createSchemaManager(); + + $options = array_keys($schemaManager->listTableColumns($sourceModel->dbSourceTable)); + asort($options); $this->util->transformFieldMappingSourceValueToSelect( array_combine($options, $options) @@ -135,6 +138,8 @@ public function initPalette(?DataContainer $dc) } } + asort($options); + $this->util->transformFieldMappingSourceValueToSelect( $options ); @@ -159,6 +164,8 @@ public function initPalette(?DataContainer $dc) $options = $source->getPostFieldsAsOptions(); + asort($options); + $this->util->transformFieldMappingSourceValueToSelect( array_combine($options, $options) ); @@ -245,7 +252,10 @@ public function getAllTargetTables(?DataContainer $dc): array } try { - $options = array_values(Database::getInstance($source->row())->listTables(null, true)); + $connection = $this->util->getDbalConnectionBySource($source->row()); + $schemaManager = $connection->createSchemaManager(); + + $options = array_values($schemaManager->listTableNames()); } catch (\Exception $e) { Message::addError(sprintf($GLOBALS['TL_LANG']['MSC']['entityImport']['dbConnectionError'], $e->getMessage())); diff --git a/src/EventListener/Contao/LoadDataContainerListener.php b/src/EventListener/Contao/LoadDataContainerListener.php new file mode 100644 index 0000000..5ccc20e --- /dev/null +++ b/src/EventListener/Contao/LoadDataContainerListener.php @@ -0,0 +1,31 @@ +utils = $utils; + } + + public function __invoke(string $table): void + { + if ($this->utils->container()->isBackend()) { + $GLOBALS['TL_CSS']['be_entityimportbundle'] = 'bundles/heimrichhannotcontaoentityimport/css/contao-entity-import-bundle-be.min.css|static'; + $GLOBALS['TL_JAVASCRIPT']['be_entityimportbundle'] = 'bundles/heimrichhannotcontaoentityimport/js/contao-entity-import-bundle-be.min.js|static'; + } + } +} diff --git a/src/HeimrichHannotContaoEntityImportBundle.php b/src/HeimrichHannotContaoEntityImportBundle.php index 7b45d87..f3750ca 100644 --- a/src/HeimrichHannotContaoEntityImportBundle.php +++ b/src/HeimrichHannotContaoEntityImportBundle.php @@ -18,4 +18,9 @@ public function getContainerExtension(): ?ExtensionInterface { return new HeimrichHannotEntityImportExtension(); } + + public function getPath(): string + { + return \dirname(__DIR__); + } } diff --git a/src/Importer/Importer.php b/src/Importer/Importer.php index 244aad9..894deda 100644 --- a/src/Importer/Importer.php +++ b/src/Importer/Importer.php @@ -12,15 +12,18 @@ use Contao\Controller; use Contao\CoreBundle\Exception\RedirectResponseException; use Contao\CoreBundle\Framework\ContaoFramework; +use Contao\CoreBundle\InsertTag\InsertTagParser; use Contao\Database; +use Contao\DcaLoader; use Contao\Email; use Contao\File; use Contao\Folder; +use Contao\Input; use Contao\Message; -use Contao\Model; use Contao\StringUtil; use Contao\System; use Contao\Validator; +use Doctrine\DBAL\Connection; use HeimrichHannot\EntityImportBundle\DataContainer\EntityImportConfigContainer; use HeimrichHannot\EntityImportBundle\Event\AfterImportEvent; use HeimrichHannot\EntityImportBundle\Event\AfterItemImportEvent; @@ -29,61 +32,51 @@ use HeimrichHannot\EntityImportBundle\Event\BeforeItemImportEvent; use HeimrichHannot\EntityImportBundle\Model\EntityImportConfigModel; use HeimrichHannot\EntityImportBundle\Source\SourceInterface; -use HeimrichHannot\RequestBundle\Component\HttpFoundation\Request; -use HeimrichHannot\UtilsBundle\Database\DatabaseUtil; -use HeimrichHannot\UtilsBundle\Dca\DcaUtil; -use HeimrichHannot\UtilsBundle\File\FileUtil; +use HeimrichHannot\EntityImportBundle\Util\EntityImportUtil; use HeimrichHannot\UtilsBundle\Util\Utils; use Psr\Log\LogLevel; use Symfony\Component\Config\Definition\Exception\Exception; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class Importer implements ImporterInterface { - protected SourceInterface $source; - protected EntityImportConfigModel $configModel; - protected bool $dryRun = false; - protected bool $webCronMode = false; + protected SourceInterface $source; + protected EntityImportConfigModel $configModel; + protected bool $dryRun = false; + protected bool $webCronMode = false; protected EventDispatcherInterface $eventDispatcher; - protected Stopwatch $stopwatch; - protected DatabaseUtil $databaseUtil; - protected DcaUtil $dcaUtil; - protected ContainerInterface $container; - protected $dbMergeCache; - protected Request $request; - protected FileUtil $fileUtil; - protected Utils $utils; - protected SymfonyStyle $io; - protected ContaoFramework $framework; + protected Stopwatch $stopwatch; + protected $dbMergeCache; + protected Utils $utils; + protected SymfonyStyle $io; + protected ContaoFramework $framework; + protected Connection $connection; + protected EntityImportUtil $entityImportUtil; + protected InsertTagParser $insertTagParser; /** * Importer constructor. */ public function __construct( - ContainerInterface $container, ContaoFramework $framework, - Model $configModel, + EntityImportConfigModel $configModel, SourceInterface $source, EventDispatcherInterface $eventDispatcher, - Request $request, - DatabaseUtil $databaseUtil, - DcaUtil $dcaUtil, - FileUtil $fileUtil, - Utils $utils + Connection $connection, + EntityImportUtil $entityImportUtil, + Utils $utils, + InsertTagParser $insertTagParser ) { - $this->container = $container; - $this->configModel = $configModel; - $this->source = $source; - $this->databaseUtil = $databaseUtil; - $this->eventDispatcher = $eventDispatcher; - $this->dcaUtil = $dcaUtil; - $this->request = $request; - $this->fileUtil = $fileUtil; - $this->utils = $utils; - $this->framework = $framework; + $this->configModel = $configModel; + $this->source = $source; + $this->eventDispatcher = $eventDispatcher; + $this->utils = $utils; + $this->framework = $framework; + $this->connection = $connection; + $this->entityImportUtil = $entityImportUtil; + $this->insertTagParser = $insertTagParser; } public function setInputOutput(SymfonyStyle $io): void @@ -98,7 +91,7 @@ public function run(): array { $this->stopwatch = new Stopwatch(); - $this->stopwatch->start('contao-entity-import-bundle.id'.$this->configModel->id); + $this->stopwatch->start('contao-entity-import-bundle.id' . $this->configModel->id); try { $items = $this->getDataFromSource(); @@ -159,7 +152,7 @@ public function getMappedItems(array $options = []): array $localizedItem = []; foreach ($mappedItem as $field => $value) { - $localizedItem[$this->dcaUtil->getLocalizedFieldName($field, $this->configModel->targetTable)] = $value; + $localizedItem[$this->entityImportUtil->getLocalizedFieldName($field, $this->configModel->targetTable)] = $value; } $mappedItem = $localizedItem; @@ -190,7 +183,7 @@ public function outputResultMessage(string $message, string $type): void $messages = json_decode($this->configModel->importProgressResult, true); $messages[] = [ - 'type' => $type, + 'type' => $type, 'message' => $message, ]; @@ -198,7 +191,7 @@ public function outputResultMessage(string $message, string $type): void } else { $this->configModel->importProgressResult = json_encode([ [ - 'type' => $type, + 'type' => $type, 'message' => $message, ], ]); @@ -243,11 +236,11 @@ public function outputFinalResultMessage(array $result): void if ('error' === $result['state']) { $message = $GLOBALS['TL_LANG']['tl_entity_import_config']['error']['errorImport'] - ."\n\n".$GLOBALS['TL_LANG']['tl_entity_import_config']['error']['error'].':'."\n\n".($result['error'] ?? ''); + . "\n\n" . $GLOBALS['TL_LANG']['tl_entity_import_config']['error']['error'] . ':' . "\n\n" . ($result['error'] ?? ''); $this->outputResultMessage($message, static::MESSAGE_TYPE_ERROR); } else { - $count = $result['count']; + $count = $result['count']; $duration = $result['duration']; $duration = str_replace('.', ',', round($duration / 1000, 2)); @@ -266,8 +259,8 @@ public function outputFinalResultMessage(array $result): void } } - if ($this->request->getGet('redirect_url')) { - throw new RedirectResponseException(html_entity_decode($this->request->getGet('redirect_url'))); + if (Input::get('redirect_url')) { + throw new RedirectResponseException(html_entity_decode(Input::get('redirect_url'))); } } @@ -284,13 +277,17 @@ public function sendErrorEmail(string $errorMessage) } if (isset($config['email']) && $config['email']) { - $email = new Email(); + $email = new Email(); $email->subject = sprintf($GLOBALS['TL_LANG']['MSC']['entityImport']['exceptionEmailSubject'], $this->configModel->title); - $email->text = sprintf('An error occurred on domain "%s"', $this->configModel->cronDomain).' : '.$errorMessage; + $email->text = sprintf('An error occurred on domain "%s"', $this->configModel->cronDomain) . ' : ' . $errorMessage; $email->sendTo($this->configModel->errorNotificationEmail ?: $GLOBALS['TL_CONFIG']['adminEmail']); } - $this->databaseUtil->update('tl_entity_import_config', ['errorNotificationLock' => '1'], 'tl_entity_import_config.id=?', [$this->configModel->id]); + $this->connection->update( + 'tl_entity_import_config', + ['errorNotificationLock' => '1'], + ['tl_entity_import_config.id' => $this->configModel->id] + ); } public function postProcess(string $table, array $mappedItems, array $dbIdMapping, array $dbItemMapping) @@ -306,9 +303,9 @@ public function postProcess(string $table, array $mappedItems, array $dbIdMappin } if (!$this->dryRun) { - $this->databaseUtil->update($table, [ - $table.'.'.$langPidField => $dbIdMapping[$itemMapping['source']['langPid']], - ], "$table.id=?", [$itemMapping['target']->id]); + $this->connection->update($table, [ + $table . '.' . $langPidField => $dbIdMapping[$itemMapping['source']['langPid']], + ], ["$table.id" => $itemMapping['target']->id]); } } } @@ -322,9 +319,9 @@ public function postProcess(string $table, array $mappedItems, array $dbIdMappin } if (!$this->dryRun) { - $this->databaseUtil->update($table, [ - $table.'.draftParent' => $dbIdMapping[$itemMapping['source']['draftParent']], - ], "$table.id=?", [$itemMapping['target']->id]); + $this->connection->update($table, [ + $table . '.draftParent' => $dbIdMapping[$itemMapping['source']['draftParent']], + ], ["$table.id" => $itemMapping['target']->id]); } } } @@ -337,16 +334,16 @@ public function postProcess(string $table, array $mappedItems, array $dbIdMappin } // map the languageMain id - $newsroomPost = $this->databaseUtil->findOneResultBy($table, [ - $table.'.'.$this->configModel->changeLanguageTargetExternalIdField.'=?', + $newsroomPost = $this->entityImportUtil->findOneResultBy($table, [ + $table . '.' . $this->configModel->changeLanguageTargetExternalIdField . '=?', ], [ $itemMapping['source']['languageMain'], ]); if (!$this->dryRun && $newsroomPost->numRows > 0) { - $this->databaseUtil->update($table, [ - $table.'.languageMain' => $newsroomPost->id, - ], "$table.id=?", [$itemMapping['target']->id]); + $this->connection->update($table, [ + $table . '.languageMain' => $newsroomPost->id, + ], ["$table.id" => $itemMapping['target']->id]); } } } @@ -354,7 +351,7 @@ public function postProcess(string $table, array $mappedItems, array $dbIdMappin $this->deleteAfterImport($mappedItems); $this->applySorting(); - $this->databaseUtil->commitTransaction(); + $this->entityImportUtil->commitTransaction(); } protected function applyFieldMappingToSourceItem(array $item, array $mapping): ?array @@ -369,7 +366,7 @@ protected function applyFieldMappingToSourceItem(array $item, array $mapping): ? if ('source_value' === $mappingElement['valueType']) { $mapped[$mappingElement['columnName']] = $item[$mappingElement['mappingValue']] ?? null; } elseif ('static_value' === $mappingElement['valueType']) { - $mapped[$mappingElement['columnName']] = $this->framework->getAdapter(Controller::class)->replaceInsertTags($mappingElement['staticValue']); + $mapped[$mappingElement['columnName']] = $this->insertTagParser->replace($mappingElement['staticValue']); } // only trim if string -> else e.g. int or null would be translated to string leading to mysql errors @@ -383,19 +380,23 @@ protected function applyFieldMappingToSourceItem(array $item, array $mapping): ? protected function executeImport(array $items): array { - $this->dcaUtil->loadLanguageFile('default'); - $this->dcaUtil->loadLanguageFile('tl_entity_import_config'); + ob_start(); + echo 111 . "\n"; + file_put_contents('/var/www/html/debug.txt', ob_get_contents(), FILE_APPEND); + ob_end_clean(); + System::loadLanguageFile('default'); + System::loadLanguageFile('tl_entity_import_config'); $database = Database::getInstance(); - $table = $this->configModel->targetTable; + $table = $this->configModel->targetTable; if (!$database->tableExists($table)) { throw new Exception($GLOBALS['TL_LANG']['tl_entity_import_config']['error']['tableDoesNotExist']); } try { - $count = 0; - $targetTableColumns = $database->getFieldNames($table); + $count = 0; + $targetTableColumns = $database->getFieldNames($table); $targetTableColumnData = []; foreach ($database->listFields($table) as $columnData) { @@ -410,7 +411,7 @@ protected function executeImport(array $items): array $mapping = $this->adjustMappingForDcMultilingual($mapping); $mapping = $this->adjustMappingForChangeLanguage($mapping); - $dbIdMapping = []; + $dbIdMapping = []; $dbItemMapping = []; if ('merge' === $mode) { @@ -431,7 +432,7 @@ protected function executeImport(array $items): array $this->deleteBeforeImport(); - $this->databaseUtil->beginTransaction(); + $this->entityImportUtil->beginTransaction(); foreach ($items as $item) { $mappedItem = $this->applyFieldMappingToSourceItem($item, $mapping); @@ -459,7 +460,7 @@ protected function executeImport(array $items): array $this->dryRun ), BeforeItemImportEvent::NAME); - $item = $event->getItem(); + $item = $event->getItem(); $mappedItem = $event->getMappedItem(); // developer can decide to skip the item in an event listener if certain criteria is met @@ -477,10 +478,10 @@ protected function executeImport(array $items): array if ('insert' === $mode) { if (!$this->dryRun) { - $statement = $this->databaseUtil->insert($table, $mappedItem); + $this->connection->insert($table, $mappedItem); - $record = (object) $mappedItem; - $record->id = $statement->insertId; + $record = (object)$mappedItem; + $record->id = $this->connection->lastInsertId(); $set = $this->setDateAdded($record); $set = array_merge($set, $this->generateAlias($record)); @@ -488,7 +489,7 @@ protected function executeImport(array $items): array $set = array_merge($set, $this->applyFieldFileMapping($record, $mappedItem)); if (!empty($set) && !$this->dryRun) { - $this->databaseUtil->update($table, $set, "$table.id=?", [$record->id]); + $this->connection->update($table, $set, ["$table.id" => $record->id]); } $importedRecord = $record; @@ -499,10 +500,10 @@ protected function executeImport(array $items): array }, $identifierFields)); if ($key && isset($this->dbMergeCache[$key]) && - ($existingRecord = $this->databaseUtil->findResultByPk($table, $this->dbMergeCache[$key])) && $existingRecord->numRows > 0) { + ($existingRecord = $this->entityImportUtil->findResultByPk($table, $this->dbMergeCache[$key])) && $existingRecord->numRows > 0) { $this->updateMappingItemForSkippedFields($mappedItem); - $existing = (object) $existingRecord->row(); + $existing = (object)$existingRecord->row(); $set = $this->setDateAdded($existing); $set = array_merge($set, $this->generateAlias($existing)); @@ -510,16 +511,16 @@ protected function executeImport(array $items): array $set = array_merge($set, $this->applyFieldFileMapping($existing, $mappedItem)); if (!$this->dryRun) { - $this->databaseUtil->update($table, array_merge($mappedItem, $set), "$table.id=?", [$existing->id]); + $this->connection->update($table, array_merge($mappedItem, $set), ["$table.id" => $existing->id]); } $importedRecord = $existing; } else { if (!$this->dryRun) { - $statement = $this->databaseUtil->insert($table, $mappedItem); + $this->connection->insert($table, $mappedItem); - $record = (object) $mappedItem; - $record->id = $statement->insertId; + $record = (object)$mappedItem; + $record->id = $this->connection->lastInsertId(); $set = $this->setDateAdded($record); $set = array_merge($set, $this->generateAlias($record)); @@ -527,7 +528,7 @@ protected function executeImport(array $items): array $set = array_merge($set, $this->applyFieldFileMapping($record, $mappedItem)); if (!empty($set) && !$this->dryRun) { - $this->databaseUtil->update($table, $set, "$table.id=?", [$record->id]); + $this->connection->update($table, $set, ["$table.id" => $record->id]); } $importedRecord = $record; @@ -544,17 +545,19 @@ protected function executeImport(array $items): array $dbItemMapping[] = [ 'source' => [ - 'langPid' => $item['langPid'] ?? null, - 'draftParent' => $item['draftParent'] ?? null, + 'langPid' => $item['langPid'] ?? null, + 'draftParent' => $item['draftParent'] ?? null, 'languageMain' => $item['languageMain'] ?? null, ], 'target' => [ - 'id' => $importedRecord->id, + 'id' => $importedRecord ? $importedRecord->id : 0, ], ]; // categories bundle - $this->importCategoryAssociations($mapping, $item, $importedRecord->id); + if (!$this->dryRun) { + $this->importCategoryAssociations($mapping, $item, $importedRecord->id); + } /* @var AfterItemImportEvent $event */ $this->eventDispatcher->dispatch(new AfterItemImportEvent( @@ -582,7 +585,7 @@ protected function executeImport(array $items): array $this->postProcess($table, $mappedItems, $dbIdMapping, $dbItemMapping); } catch (\Exception $e) { - $this->stopwatch->stop('contao-entity-import-bundle.id'.$this->configModel->id); + $this->stopwatch->stop('contao-entity-import-bundle.id' . $this->configModel->id); $this->sendErrorEmail($e->getMessage()); @@ -592,20 +595,20 @@ protected function executeImport(array $items): array ]; } - $event = $this->stopwatch->stop('contao-entity-import-bundle.id'.$this->configModel->id); + $event = $this->stopwatch->stop('contao-entity-import-bundle.id' . $this->configModel->id); $duration = $event->getDuration(); if ($this->configModel->errorNotificationLock) { - $this->databaseUtil->update('tl_entity_import_config', ['errorNotificationLock' => ''], 'tl_entity_import_config.id=?', [$this->configModel->id]); + $this->connection->update('tl_entity_import_config', ['errorNotificationLock' => ''], ['tl_entity_import_config.id' => $this->configModel->id]); } return [ - 'state' => 'success', - 'count' => $count, - 'duration' => $duration, - 'mappedItems' => $mappedItems, - 'dbIdMapping' => $dbIdMapping, + 'state' => 'success', + 'count' => $count, + 'duration' => $duration, + 'mappedItems' => $mappedItems, + 'dbIdMapping' => $dbIdMapping, 'dbItemMapping' => $dbItemMapping, ]; } @@ -654,7 +657,7 @@ protected function importCategoryAssociations(array $mapping, array $item, $targ $categoryManager = System::getContainer()->get('huh.categories.manager'); - $table = $this->configModel->targetTable; + $table = $this->configModel->targetTable; $sourceTable = $this->source->getSourceModel()->dbSourceTable; $dca = &$GLOBALS['TL_DCA'][$table]; @@ -726,13 +729,13 @@ protected function initDbCacheForMerge(array $mergeIdentifiers) if ($this->configModel->mergeIdentifierAdditionalWhere) { $columns = [html_entity_decode($this->configModel->mergeIdentifierAdditionalWhere)]; - $values = []; + $values = []; } else { $columns = null; - $values = null; + $values = null; } - if (null === ($records = $this->databaseUtil->findResultsBy($table, $columns, $values)) || $records->numRows < 1) { + if (null === ($records = $this->entityImportUtil->findResultsBy($table, $columns, $values)) || $records->numRows < 1) { $this->dbMergeCache = []; return; @@ -763,7 +766,7 @@ protected function initDbCacheForMerge(array $mergeIdentifiers) protected function getDebugConfig(): ?array { - $config = $this->container->getParameter('huh_entity_import'); + $config = System::getContainer()->getParameter('huh_entity_import'); return $config['debug'] ?? []; } @@ -773,7 +776,7 @@ protected function deleteBeforeImport() $table = $this->configModel->targetTable; if ($this->configModel->deleteBeforeImport && !$this->dryRun) { - $this->databaseUtil->delete($table, html_entity_decode($this->configModel->deleteBeforeImportWhere)); + $this->connection->executeStatement("DELETE FROM $table WHERE " . html_entity_decode($this->configModel->deleteBeforeImportWhere)); } } @@ -795,29 +798,29 @@ protected function deleteAfterImport(array $mappedItems) $identifiers = ''; foreach ($mappedItems as $value) { - $identifiers .= '"'.$value[$deletionIdentifier['target']].'",'; + $identifiers .= '"' . $value[$deletionIdentifier['target']] . '",'; } $identifiers = rtrim($identifiers, ','); if ($identifiers) { - $conditions[] = '('.$table.'.'.$deletionIdentifier['target'].' NOT IN ('.$identifiers.'))'; + $conditions[] = '(' . $table . '.' . $deletionIdentifier['target'] . ' NOT IN (' . $identifiers . '))'; } } if ($this->configModel->targetDeletionAdditionalWhere) { - $conditions[] = '('.html_entity_decode($this->configModel->targetDeletionAdditionalWhere).')'; + $conditions[] = '(' . html_entity_decode($this->configModel->targetDeletionAdditionalWhere) . ')'; } if (!$this->dryRun && !empty($conditions)) { - $this->databaseUtil->delete($table, implode(' AND ', $conditions), []); + $this->connection->executeStatement("DELETE FROM $table WHERE " . implode(' AND ', $conditions)); } break; case EntityImportConfigContainer::DELETION_MODE_TARGET_FIELDS: if ($this->configModel->deleteBeforeImport && !$this->dryRun) { - $this->databaseUtil->delete($table, html_entity_decode($this->configModel->targetDeletionWhere)); + $this->connection->executeStatement("DELETE FROM $table WHERE " . html_entity_decode($this->configModel->targetDeletionWhere)); } break; @@ -861,8 +864,8 @@ protected function setTstamp($record): array protected function generateAlias($record): array { - $table = $this->configModel->targetTable; - $field = $this->configModel->targetAliasField; + $table = $this->configModel->targetTable; + $field = $this->configModel->targetAliasField; $fieldPattern = $this->configModel->aliasFieldPattern; if (!$this->configModel->generateAlias || !$field || !$fieldPattern || !$record->id) { @@ -877,7 +880,7 @@ function ($matches) use ($record) { $fieldPattern ); - $alias = $this->dcaUtil->generateAlias( + $alias = $this->entityImportUtil->generateAlias( $record->{$field} ?? '', $record->id, $table, @@ -893,18 +896,18 @@ function ($matches) use ($record) { protected function applyFieldFileMapping($record, $item): array { - $set = []; + $set = []; $slugGenerator = new SlugGenerator(); - $fileMapping = StringUtil::deserialize($this->configModel->fileFieldMapping, true); + $fileMapping = StringUtil::deserialize($this->configModel->fileFieldMapping, true); foreach ($fileMapping as $mapping) { - if (($record->{$mapping['targetField']} ?? NULL) && $mapping['skipIfExisting']) { + if (($record->{$mapping['targetField']} ?? null) && $mapping['skipIfExisting']) { continue; } // retrieve the file try { - $content = $this->fileUtil->retrieveFileContent( + $content = $this->entityImportUtil->retrieveFileContent( $item[$mapping['mappingField']], $this->utils->container()->isBackend() ); } catch (\Exception $e) { @@ -915,7 +918,7 @@ protected function applyFieldFileMapping($record, $item): array // sleep after http requests because of a possible rate limiting if (Validator::isUrl($item[$mapping['mappingField']]) && $mapping['delayAfter'] > 0) { - sleep((int) ($mapping['delayAfter'])); + sleep((int)($mapping['delayAfter'])); } // no file found? @@ -948,16 +951,16 @@ function ($matches) use ($record) { $filename = $slugGenerator->generate($filename); } - $extension = $this->fileUtil->getExtensionFromFileContent($content); + $extension = $this->entityImportUtil->getExtensionFromFileContent($content); - $extension = $extension ? '.'.$extension : ''; + $extension = $extension ? '.' . $extension : ''; // check if a file of that name already exists - $folder = new Folder($this->fileUtil->getPathFromUuid($mapping['targetFolder'])); + $folder = new Folder($this->utils->file()->getPathFromUuid($mapping['targetFolder'])); - $filenameWithoutExtension = $folder->path.'/'.$filename; + $filenameWithoutExtension = $folder->path . '/' . $filename; - $file = new File($filenameWithoutExtension.$extension); + $file = new File($filenameWithoutExtension . $extension); if ($file->exists()) { if (!isset($record->{$mapping['targetField']}) || !$record->{$mapping['targetField']}) { @@ -965,8 +968,8 @@ function ($matches) use ($record) { $i = 1; while ($file->exists()) { - $filenameWithoutExtension .= '-'.$i++; - $file = new File($filenameWithoutExtension.$extension); + $filenameWithoutExtension .= '-' . $i++; + $file = new File($filenameWithoutExtension . $extension); } } else { // only rewrite if content has changed @@ -979,7 +982,7 @@ function ($matches) use ($record) { $event = $this->eventDispatcher->dispatch(new BeforeFileImportEvent( $file->path, $content, - (array) $record, + (array)$record, $item, $this->configModel, $this, @@ -1004,7 +1007,7 @@ protected function applySorting() { $field = $this->configModel->targetSortingField; - $where = $this->framework->getAdapter(Controller::class)->replaceInsertTags( + $where = $this->insertTagParser->replace( html_entity_decode($this->configModel->targetSortingContextWhere), false ); @@ -1018,7 +1021,7 @@ protected function applySorting() switch ($this->configModel->sortingMode) { case EntityImportConfigContainer::SORTING_MODE_TARGET_FIELDS: - $results = $this->databaseUtil->findResultsBy($table, [$where], [], [ + $results = $this->entityImportUtil->findResultsBy($table, [$where], [], [ 'order' => $order, ]); @@ -1033,9 +1036,9 @@ protected function applySorting() continue; } - $this->databaseUtil->update($table, [ + $this->connection->update($table, [ $field => $count++ * 128, - ], "$table.id=?", [$results->id]); + ], ["$table.id" => $results->id]); } break; @@ -1051,22 +1054,23 @@ protected function adjustMappingForDcMultilingual(array $mapping) $table = $this->configModel->targetTable; - $this->dcaUtil->loadDc($table); + $loader = new DcaLoader($table); + $loader->load(); $dca = $GLOBALS['TL_DCA'][$table]; - $langPidField = $dca['config']['langPid'] ?? 'langPid'; + $langPidField = $dca['config']['langPid'] ?? 'langPid'; $languageField = $dca['config']['langColumnName'] ?? 'language'; $mapping[] = [ - 'columnName' => $langPidField, - 'valueType' => 'source_value', + 'columnName' => $langPidField, + 'valueType' => 'source_value', 'mappingValue' => 'langPid', ]; $mapping[] = [ - 'columnName' => $languageField, - 'valueType' => 'source_value', + 'columnName' => $languageField, + 'valueType' => 'source_value', 'mappingValue' => 'language', ]; @@ -1074,24 +1078,24 @@ protected function adjustMappingForDcMultilingual(array $mapping) $publishedField = $dca['config']['langPublished'] ?? 'langPublished'; $mapping[] = [ - 'columnName' => $publishedField, - 'valueType' => 'source_value', + 'columnName' => $publishedField, + 'valueType' => 'source_value', 'mappingValue' => 'langPublished', ]; if ($dca['config']['langStart']) { $publishedStartField = $dca['config']['langStart'] ?? 'langStart'; - $publishedStopField = $dca['config']['langStop'] ?? 'langStop'; + $publishedStopField = $dca['config']['langStop'] ?? 'langStop'; $mapping[] = [ - 'columnName' => $publishedStartField, - 'valueType' => 'source_value', + 'columnName' => $publishedStartField, + 'valueType' => 'source_value', 'mappingValue' => 'langStart', ]; $mapping[] = [ - 'columnName' => $publishedStopField, - 'valueType' => 'source_value', + 'columnName' => $publishedStopField, + 'valueType' => 'source_value', 'mappingValue' => 'langStop', ]; } @@ -1107,8 +1111,8 @@ protected function adjustMappingForChangeLanguage(array $mapping) } $mapping[] = [ - 'columnName' => 'languageMain', - 'valueType' => 'source_value', + 'columnName' => 'languageMain', + 'valueType' => 'source_value', 'mappingValue' => 'languageMain', ]; diff --git a/src/Importer/ImporterFactory.php b/src/Importer/ImporterFactory.php index b25a687..27619c0 100644 --- a/src/Importer/ImporterFactory.php +++ b/src/Importer/ImporterFactory.php @@ -9,63 +9,52 @@ namespace HeimrichHannot\EntityImportBundle\Importer; use Contao\CoreBundle\Framework\ContaoFramework; +use Contao\CoreBundle\InsertTag\InsertTagParser; +use Doctrine\DBAL\Connection; use HeimrichHannot\EntityImportBundle\Source\SourceFactory; use HeimrichHannot\EntityImportBundle\Source\SourceInterface; -use HeimrichHannot\RequestBundle\Component\HttpFoundation\Request; -use HeimrichHannot\UtilsBundle\Database\DatabaseUtil; -use HeimrichHannot\UtilsBundle\Dca\DcaUtil; -use HeimrichHannot\UtilsBundle\File\FileUtil; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; +use HeimrichHannot\EntityImportBundle\Util\EntityImportUtil; use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class ImporterFactory { - protected DatabaseUtil $databaseUtil; protected EventDispatcherInterface $eventDispatcher; - protected ModelUtil $modelUtil; - protected SourceFactory $sourceFactory; - protected DcaUtil $dcaUtil; - protected ContainerInterface $container; - protected Request $request; - protected FileUtil $fileUtil; - protected Utils $utils; - protected ContaoFramework $framework; + protected SourceFactory $sourceFactory; + protected Utils $utils; + protected ContaoFramework $framework; + protected Connection $connection; + protected EntityImportUtil $entityImportUtil; + protected InsertTagParser $insertTagParser; public function __construct( - ContainerInterface $container, ContaoFramework $framework, - DatabaseUtil $databaseUtil, EventDispatcherInterface $eventDispatcher, - Request $request, - ModelUtil $modelUtil, - DcaUtil $dcaUtil, + Connection $connection, + EntityImportUtil $entityImportUtil, SourceFactory $sourceFactory, - FileUtil $fileUtil, - Utils $utils + Utils $utils, + InsertTagParser $insertTagParser ) { - $this->databaseUtil = $databaseUtil; $this->eventDispatcher = $eventDispatcher; - $this->modelUtil = $modelUtil; $this->sourceFactory = $sourceFactory; - $this->dcaUtil = $dcaUtil; - $this->container = $container; - $this->request = $request; - $this->fileUtil = $fileUtil; $this->utils = $utils; $this->framework = $framework; + $this->connection = $connection; + $this->entityImportUtil = $entityImportUtil; + $this->insertTagParser = $insertTagParser; } public function createInstance($configModel, array $options = []): ?ImporterInterface { $sourceModel = $options['sourceModel'] ?? null; - if (is_numeric($configModel) && null === ($configModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_config', $configModel))) { + if (is_numeric($configModel) && null === ($configModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_config', $configModel))) { return null; } - if (null === $sourceModel && null === ($sourceModel = $this->modelUtil->findModelInstanceByPk('tl_entity_import_source', $configModel->pid))) { + if (null === $sourceModel && null === ($sourceModel = $this->utils->model()->findModelInstanceByPk('tl_entity_import_source', $configModel->pid))) { return null; } @@ -78,16 +67,14 @@ public function createInstance($configModel, array $options = []): ?ImporterInte $source->setDomain($configModel->cronDomain); return new Importer( - $this->container, $this->framework, $configModel, $source, $this->eventDispatcher, - $this->request, - $this->databaseUtil, - $this->dcaUtil, - $this->fileUtil, - $this->utils + $this->connection, + $this->entityImportUtil, + $this->utils, + $this->insertTagParser ); } } diff --git a/src/Resources/public/assets/contao-entity-import-bundle-be.css b/src/Resources/public/assets/contao-entity-import-bundle-be.css deleted file mode 100644 index 96126b2..0000000 --- a/src/Resources/public/assets/contao-entity-import-bundle-be.css +++ /dev/null @@ -1 +0,0 @@ -#tl_entity_import_source [name=fileContent]+.ace_editor{height:500px!important} \ No newline at end of file diff --git a/src/Resources/public/assets/contao-entity-import-bundle-be.js b/src/Resources/public/assets/contao-entity-import-bundle-be.js deleted file mode 100644 index 21c149d..0000000 --- a/src/Resources/public/assets/contao-entity-import-bundle-be.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/bundles/heimrichhannotcontaoentityimportbundle/assets/",n(n.s="6Yoe")}({"6Yoe":function(e,t,n){"use strict";n.r(t);n("niFc");function r(e,t){for(var n=0;nstringUtil->replaceInsertTags($mappingElement['staticValue']); + $result[$mappingElement['name']] = $this->container->get(InsertTagParser::class)->replaceInsertTags($mappingElement['staticValue']); } elseif ('source_value' === $mappingElement['valueType']) { $result[$mappingElement['name']] = $element[$mappingElement['sourceValue']]; } diff --git a/src/Source/DatabaseSource.php b/src/Source/DatabaseSource.php index 6fd8464..126df21 100644 --- a/src/Source/DatabaseSource.php +++ b/src/Source/DatabaseSource.php @@ -8,14 +8,23 @@ namespace HeimrichHannot\EntityImportBundle\Source; -use Contao\Controller; use Contao\Database; use Contao\DcaLoader; use Contao\StringUtil; -use HeimrichHannot\UtilsBundle\Dca\DcaUtil; +use Doctrine\DBAL\Connection; +use HeimrichHannot\EntityImportBundle\Util\EntityImportUtil; class DatabaseSource extends AbstractSource { + protected EntityImportUtil $util; + + public function __construct(EntityImportUtil $util) + { + $this->util = $util; + + parent::__construct(); + } + public function getMappedData(array $options = []): array { $sourceModel = $this->sourceModel; @@ -25,15 +34,16 @@ public function getMappedData(array $options = []): array $mapping = $this->adjustMappingForChangeLanguage($mapping); // retrieve the source records - $db = Database::getInstance($sourceModel->row()); $where = $sourceModel->dbSourceTableWhere ?: '1=1'; - $records = $db->prepare("SELECT * FROM $sourceModel->dbSourceTable WHERE $where")->execute(); + $connection = $this->util->getDbalConnectionBySource($sourceModel->row()); + + $records = $connection->prepare("SELECT * FROM $sourceModel->dbSourceTable WHERE $where")->executeQuery()->fetchAllAssociative(); $data = []; - while ($records->next()) { - $data[] = $this->getMappedItemData($records->row(), $mapping); + foreach ($records as $record) { + $data[] = $this->getMappedItemData($record, $mapping); } return $data; diff --git a/src/Source/SourceFactory.php b/src/Source/SourceFactory.php index b0edbdc..4cc1f2a 100644 --- a/src/Source/SourceFactory.php +++ b/src/Source/SourceFactory.php @@ -10,28 +10,29 @@ use Contao\CoreBundle\InsertTag\InsertTagParser; use Contao\StringUtil; +use Contao\System; use HeimrichHannot\EntityImportBundle\DataContainer\EntityImportSourceContainer; use HeimrichHannot\EntityImportBundle\Event\SourceFactoryCreateSourceEvent; +use HeimrichHannot\EntityImportBundle\Util\EntityImportUtil; use HeimrichHannot\UtilsBundle\Util\Utils; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class SourceFactory { - protected ContainerInterface $container; protected EventDispatcherInterface $eventDispatcher; protected Utils $utils; protected InsertTagParser $insertTagParser; + protected EntityImportUtil $entityImportUtil; /** * SourceFactory constructor. */ - public function __construct(ContainerInterface $container, EventDispatcherInterface $eventDispatcher, Utils $utils, InsertTagParser $insertTagParser) + public function __construct(EventDispatcherInterface $eventDispatcher, Utils $utils, InsertTagParser $insertTagParser, EntityImportUtil $entityImportUtil) { $this->eventDispatcher = $eventDispatcher; - $this->container = $container; $this->utils = $utils; $this->insertTagParser = $insertTagParser; + $this->entityImportUtil = $entityImportUtil; } public function createInstance(int $sourceModel): ?SourceInterface @@ -44,7 +45,7 @@ public function createInstance(int $sourceModel): ?SourceInterface switch ($sourceModel->type) { case EntityImportSourceContainer::TYPE_DATABASE: - $source = new DatabaseSource(); + $source = new DatabaseSource($this->entityImportUtil); break; @@ -87,7 +88,7 @@ public function createInstance(int $sourceModel): ?SourceInterface $source->setFieldMapping(StringUtil::deserialize($sourceModel->fieldMapping, true)); $source->setSourceModel($sourceModel); - $source->setContainer($this->container); + $source->setContainer(System::getContainer()); return $source; } diff --git a/src/Util/EntityImportUtil.php b/src/Util/EntityImportUtil.php index d47be55..5a5c331 100644 --- a/src/Util/EntityImportUtil.php +++ b/src/Util/EntityImportUtil.php @@ -8,11 +8,32 @@ namespace HeimrichHannot\EntityImportBundle\Util; +use Contao\CoreBundle\Framework\ContaoFramework; +use Contao\Database; use Contao\DcaLoader; +use Contao\File; +use Contao\Message; +use Contao\StringUtil; use Contao\System; +use Contao\Validator; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DriverManager; +use GuzzleHttp\Client; +use HeimrichHannot\UtilsBundle\Util\Utils; class EntityImportUtil { + protected ContaoFramework $contaoFramework; + protected Connection $connection; + protected Utils $utils; + + public function __construct(ContaoFramework $contaoFramework, Connection $connection, Utils $utils) + { + $this->contaoFramework = $contaoFramework; + $this->connection = $connection; + $this->utils = $utils; + } + public function transformFieldMappingSourceValueToSelect($options) { $dca = &$GLOBALS['TL_DCA']['tl_entity_import_source']['fields']['fieldMapping']['eval']['multiColumnEditor']['fields']['sourceValue']; @@ -24,6 +45,35 @@ public function transformFieldMappingSourceValueToSelect($options) $dca['eval']['chosen'] = true; } + public function getDbalConnectionBySource(array $sourceParams): Connection + { + $mapping = [ + 'dbDriver' => 'driver', + 'dbHost' => 'host', + 'dbUser' => 'user', + 'dbPass' => 'password', + 'dbDatabase' => 'dbname', + 'dbPconnect' => 'persistent', + 'dbCharset' => 'charset', + 'dbPort' => 'port', + 'dbSocket' => 'unix_socket', + ]; + + $dbalParams = []; + + foreach ($sourceParams as $k => $v) { + if (isset($mapping[$k])) { + $dbalParams[$mapping[$k]] = $v; + } + } + + $dbalParams['persistent'] = $dbalParams['persistent'] === 'true'; + + $connection = DriverManager::getConnection($dbalParams); + + return $connection; + } + public function getLocalizedFieldName(string $strField, string $strTable): ?string { $loader = new DcaLoader($strTable); @@ -32,4 +82,369 @@ public function getLocalizedFieldName(string $strField, string $strTable): ?stri return $GLOBALS['TL_DCA'][$strTable]['fields'][$strField]['label'][0] ?: $strField; } + + public function beginTransaction(): void + { +// $this->connection->beginTransaction(); + } + + public function commitTransaction(): void + { +// $this->connection->commit(); + } + + /** + * Returns a database result for a given table and id(primary key). + * + * @param mixed $pk + * + * @return mixed + */ + public function findResultByPk(string $table, $pk, array $options = []) + { + /* @var Database $db */ + if (!($db = $this->contaoFramework->getAdapter(Database::class))) { + return null; + } + + $options = array_merge( + [ + 'limit' => 1, + 'column' => 'id', + 'value' => $pk, + ], + $options + ); + + $options['table'] = $table; + + if (isset($options['selectFields'])) { + $query = $this->createQueryWithoutRelations($options['selectFields']); + } else { + $query = \Contao\Model\QueryBuilder::find($options); + } + + $statement = $db->getInstance()->prepare($query); + + // Defaults for limit and offset + if (!isset($options['limit'])) { + $options['limit'] = 0; + } + + if (!isset($options['offset'])) { + $options['offset'] = 0; + } + + // Limit + if ($options['limit'] > 0 || $options['offset'] > 0) { + $statement->limit($options['limit'], $options['offset']); + } + + return $statement->execute($options['value']); + } + + /** + * Return a single database result by table and search criteria. + * + * @return mixed + */ + public function findOneResultBy(string $table, ?array $columns, ?array $values, array $options = []) + { + /* @var Database $db */ + if (!($db = $this->contaoFramework->getAdapter(Database::class))) { + return null; + } + + $options = array_merge( + [ + 'limit' => 1, + 'column' => $columns, + 'value' => $values, + ], + $options + ); + + $options['table'] = $table; + + if (isset($options['selectFields'])) { + $query = $this->createQueryWithoutRelations($options['selectFields']); + } else { + $query = \Contao\Model\QueryBuilder::find($options); + } + + $statement = $db->getInstance()->prepare($query); + + if (!isset($options['offset'])) { + $options['offset'] = 0; + } + + // Limit + if ($options['limit'] > 0 || $options['offset'] > 0) { + $statement->limit($options['limit'], $options['offset']); + } + + return $statement->execute($options['value']); + } + + public function findResultsBy(string $table, ?array $columns, ?array $values, array $options = []) + { + /* @var Database $db */ + if (!($db = $this->contaoFramework->getAdapter(Database::class))) { + return null; + } + + if (null !== $columns) { + $options = array_merge( + [ + 'column' => $columns, + ], + $options + ); + } + + if (null !== $values) { + $options = array_merge( + [ + 'value' => $values, + ], + $options + ); + } + + $options['table'] = $table; + + if (isset($options['selectFields'])) { + $query = $this->createQueryWithoutRelations($options); + } else { + $query = \Contao\Model\QueryBuilder::find($options); + } + + $statement = $db->getInstance()->prepare($query); + + // Defaults for limit and offset + if (!isset($options['limit'])) { + $options['limit'] = 0; + } + + if (!isset($options['offset'])) { + $options['offset'] = 0; + } + + // Limit + if ($options['limit'] > 0 || $options['offset'] > 0) { + $statement->limit($options['limit'], $options['offset']); + } + + return isset($options['value']) ? $statement->execute($options['value']) : $statement->execute(); + } + + /** + * Adapted from \Contao\Model\QueryBuilder::find(). + * + * @return string + */ + private function createQueryWithoutRelations(array $options) + { + $fields = $options['selectFields'] ?? []; + + $query = 'SELECT '.(empty($fields) ? '*' : implode(', ', $fields)).' FROM '.$options['table']; + + // Where condition + if (isset($options['column'])) { + $query .= ' WHERE '.(\is_array($options['column']) ? implode(' AND ', $options['column']) : $options['table'].'.'.Database::quoteIdentifier($options['column']).'=?'); + } + + // Group by + if (isset($options['group'])) { + @trigger_error('Using the "group" option has been deprecated and will no longer work in Contao 5.0. See https://github.com/contao/contao/issues/1680', \E_USER_DEPRECATED); + $query .= ' GROUP BY '.$options['group']; + } + + // Having (see #6446) + if (isset($options['having'])) { + $query .= ' HAVING '.$options['having']; + } + + // Order by + if (isset($options['order'])) { + $query .= ' ORDER BY '.$options['order']; + } + + return $query; + } + + /** + * Return if the current alias already exist in table. + */ + public function aliasExist(string $alias, int $id, string $table, $options = []): bool + { + $aliasField = $options['aliasField'] ?? 'alias'; + + $stmt = $this->connection->prepare('SELECT id FROM '.$table.' WHERE '.$aliasField.'=? AND id!=?'); + + return $stmt->executeQuery([$alias, $id])->rowCount() > 0; + } + + /** + * Generate an alias with unique check. + * + * @param mixed $alias The current alias (if available) + * @param int $id The entity's id + * @param string|null $table The entity's table (pass a comma separated list if the validation should be expanded to multiple tables like tl_news AND tl_member. ATTENTION: the first table needs to be the one we're currently in). Pass null to skip unqiue check. + * @param string $title The value to use as a base for the alias + * @param bool $keepUmlauts Set to true if German umlauts should be kept + * + * @throws \Exception + * + * @return string + */ + public function generateAlias(?string $alias, int $id, ?string $table, string $title, bool $keepUmlauts = true, $options = []): string + { + $autoAlias = false; + $aliasField = $options['aliasField'] ?? 'alias'; + + // Generate alias if there is none + if (empty($alias)) { + $autoAlias = true; + $alias = StringUtil::generateAlias($title); + } + + if (!$keepUmlauts) { + $alias = preg_replace(['/ä/i', '/ö/i', '/ü/i', '/ß/i'], ['ae', 'oe', 'ue', 'ss'], $alias); + } + + if (null === $table) { + return $alias; + } + + $originalAlias = $alias; + + // multiple tables? + if (false !== strpos($table, ',')) { + $tables = explode(',', $table); + + foreach ($tables as $i => $partTable) { + // the table in which the entity is + if (0 === $i) { + if ($this->aliasExist($alias, $id, $table, $options)) { + if (!$autoAlias) { + throw new \InvalidArgumentException(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $alias)); + } + + $alias = $originalAlias.'-'.$id; + } + } else { + // another table + $stmt = $this->connection->prepare("SELECT id FROM {$partTable} WHERE ' . $aliasField . '=?"); + + // Check whether the alias exists + if ($stmt->executeQuery([$alias])->rowCount() > 0) { + throw new \InvalidArgumentException(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $alias)); + } + } + } + } else { + if (!$this->aliasExist($alias, $id, $table, $options)) { + return $alias; + } + + // Check whether the alias exists + if (!$autoAlias) { + throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $alias)); + } + + // Add ID to alias + $alias .= '-'.$id; + } + + return $alias; + } + + public function getExtensionFromFileContent($content): string|false + { + if (!class_exists('\finfo')) { + return false; + } + + $finfo = new \finfo(\FILEINFO_MIME_TYPE); + + return $this->getExtensionByMimeType($finfo->buffer($content)); + } + + public function getExtensionByMimeType($mimeType): string|false + { + foreach ($GLOBALS['TL_MIME'] as $extension => $data) { + if ($data[0] === $mimeType) { + if ('jpeg' === $extension) { + $extension = 'jpg'; + } + + return $extension; + } + } + + return false; + } + + /** + * Tries to get the binary content from a file in various sources and returns it if possible. + * + * Possible sources: + * - url + * - contao uuid + * - string is already a binary file content + * + * @param $source + * + * @return bool|mixed Returns false if the file content could not be retrieved + */ + public function retrieveFileContent($source, $silent = true): mixed + { + // url + if (Validator::isUrl($source)) { + $client = new Client(); + $request = $client->request('GET', $source); + + if (200 === $request->getStatusCode()) { + $content = $request->getBody()->__toString(); + + if ($content) { + return $content; + } + } else { + if (!$silent) { + $body = $request->getBody()->__toString(); + + Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['httpRequestError'], $source, 'Code '.$request->getStatusCode().': '.$body)); + } + } + } + + // contao uuid + if (Validator::isUuid($source)) { + $content = $this->getFileContentFromUuid($source); + + if (false !== $content) { + return $content; + } + } + + // already binary -> ctype_print() checks if non-printable characters are in the string -> if so, it's most likely a file + if (!ctype_print($source)) { + return $source; + } + + return false; + } + + public function getFileContentFromUuid($uuid): false|string + { + $file = new File($this->utils->file()->getPathFromUuid($uuid)); + + if (!$file || !$file->exists()) { + return false; + } + + return file_get_contents(System::getContainer()->getParameter('kernel.project_dir').'/'.$file->path); + } } From a755a72c670a948d010661db93edb0690e26b7bc Mon Sep 17 00:00:00 2001 From: dpa Date: Tue, 9 Sep 2025 09:20:47 +0200 Subject: [PATCH 3/6] feat: refactorings for contao 5 --- contao/dca/tl_entity_import_quick_config.php | 2 +- src/DataContainer/EntityImportQuickConfigContainer.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contao/dca/tl_entity_import_quick_config.php b/contao/dca/tl_entity_import_quick_config.php index b9493dd..be11b1e 100644 --- a/contao/dca/tl_entity_import_quick_config.php +++ b/contao/dca/tl_entity_import_quick_config.php @@ -78,7 +78,7 @@ 'label' => &$GLOBALS['TL_LANG']['tl_entity_import_quick_config']['import'], 'href' => 'key=import', 'icon' => 'theme_import.svg', - 'attributes' => 'onclick="if (!confirm(\''.$GLOBALS['TL_LANG']['tl_entity_import_config']['importConfirm'].'\')) return false; Backend.getScrollOffset();"', + 'attributes' => 'data-turbo="false" onclick="if (!confirm(\''.$GLOBALS['TL_LANG']['tl_entity_import_config']['importConfirm'].'\')) return false; Backend.getScrollOffset();"', ], ], ], diff --git a/src/DataContainer/EntityImportQuickConfigContainer.php b/src/DataContainer/EntityImportQuickConfigContainer.php index 74a9a9c..498f972 100644 --- a/src/DataContainer/EntityImportQuickConfigContainer.php +++ b/src/DataContainer/EntityImportQuickConfigContainer.php @@ -87,7 +87,7 @@ public function getDryRunOperation($row, $href, $label, $title, $icon, $attribut } } - return ''.Image::getHtml($icon, $label).' '; + return ''.Image::getHtml($icon, $label).' '; } public function modifyDca(DataContainer $dc) From 023cf5e9cdf801b89f6b5a0ab9240c8ee940de67 Mon Sep 17 00:00:00 2001 From: dpa Date: Tue, 9 Sep 2025 09:47:35 +0200 Subject: [PATCH 4/6] feat: refactorings for contao 5 --- src/Command/ExecuteImportCommand.php | 2 +- src/DataContainer/EntityImportConfigContainer.php | 7 +++++-- src/DataContainer/EntityImportQuickConfigContainer.php | 7 +++++-- src/Importer/Importer.php | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Command/ExecuteImportCommand.php b/src/Command/ExecuteImportCommand.php index a4e9ace..8d6bcd9 100644 --- a/src/Command/ExecuteImportCommand.php +++ b/src/Command/ExecuteImportCommand.php @@ -82,7 +82,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->input = $input; $this->io = new SymfonyStyle($input, $output); - $this->container->get('contao.framework')->initialize(); + $this->framework->initialize(); $this->import(); diff --git a/src/DataContainer/EntityImportConfigContainer.php b/src/DataContainer/EntityImportConfigContainer.php index 32b64cc..f115951 100644 --- a/src/DataContainer/EntityImportConfigContainer.php +++ b/src/DataContainer/EntityImportConfigContainer.php @@ -218,13 +218,16 @@ private function runImport(bool $dry = false) $configModel->importProgressResult = ''; $configModel->save(); - throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('act=edit', $this->utils->url()->removeQueryStringParameterFromUrl(['key']))); + throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('act=edit', $this->utils->url()->removeQueryStringParameterFromUrl('key'))); } $importer = $this->importerFactory->createInstance($configModel->id); $importer->setDryRun($dry); $result = $importer->run(); $importer->outputFinalResultMessage($result); - throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('id=' . $sourceModel->id, $this->utils->url()->removeQueryStringParameterFromUrl(['key', 'id']))); + $url = $this->utils->url()->removeQueryStringParameterFromUrl('key'); + $url = $this->utils->url()->removeQueryStringParameterFromUrl('id', $url); + + throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('id=' . $sourceModel->id, $url)); } } diff --git a/src/DataContainer/EntityImportQuickConfigContainer.php b/src/DataContainer/EntityImportQuickConfigContainer.php index 498f972..a36b649 100644 --- a/src/DataContainer/EntityImportQuickConfigContainer.php +++ b/src/DataContainer/EntityImportQuickConfigContainer.php @@ -406,13 +406,16 @@ private function runImport(bool $dry = false) $importerConfig->importProgressResult = ''; $importerConfig->save(); - throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('act=edit', $this->utils->url()->removeQueryStringParameterFromUrl(['key']))); + throw new RedirectResponseException($this->utils->url()->addQueryStringParameterToUrl('act=edit', $this->utils->url()->removeQueryStringParameterFromUrl('key'))); } $importer->setDryRun($dry); $result = $importer->run(); $importer->outputFinalResultMessage($result); - throw new RedirectResponseException($this->utils->url()->removeQueryStringParameterFromUrl(['key', 'id'])); + $url = $this->utils->url()->removeQueryStringParameterFromUrl('key'); + $url = $this->utils->url()->removeQueryStringParameterFromUrl('id', $url); + + throw new RedirectResponseException($url); } /** diff --git a/src/Importer/Importer.php b/src/Importer/Importer.php index 894deda..c2a1042 100644 --- a/src/Importer/Importer.php +++ b/src/Importer/Importer.php @@ -832,7 +832,7 @@ protected function setDateAdded($record): array $table = $this->configModel->targetTable; $field = $this->configModel->targetDateAddedField; - if (!$this->configModel->setDateAdded || !$field || $record->{$field} || !$record->id) { + if (!$this->configModel->setDateAdded || !$field || ($record->{$field} ?? false) || !$record->id) { return []; } From 2d05aaca8a1eb6961c06951527351e88a243a873 Mon Sep 17 00:00:00 2001 From: dpa Date: Wed, 10 Sep 2025 16:05:02 +0200 Subject: [PATCH 5/6] feat: refactorings for contao 5 --- src/Importer/Importer.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Importer/Importer.php b/src/Importer/Importer.php index c2a1042..95f6242 100644 --- a/src/Importer/Importer.php +++ b/src/Importer/Importer.php @@ -380,10 +380,6 @@ protected function applyFieldMappingToSourceItem(array $item, array $mapping): ? protected function executeImport(array $items): array { - ob_start(); - echo 111 . "\n"; - file_put_contents('/var/www/html/debug.txt', ob_get_contents(), FILE_APPEND); - ob_end_clean(); System::loadLanguageFile('default'); System::loadLanguageFile('tl_entity_import_config'); From b449f7bfb668a9d49251e666a57e668800af5e2d Mon Sep 17 00:00:00 2001 From: dpa Date: Sat, 13 Sep 2025 21:29:16 +0200 Subject: [PATCH 6/6] refactor: rename JobContainer and JobArchiveContainer to JobEntryContainer and JobEntryArchiveContainer; update references accordingly --- src/DataContainer/EntityImportQuickConfigContainer.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/DataContainer/EntityImportQuickConfigContainer.php b/src/DataContainer/EntityImportQuickConfigContainer.php index a36b649..4d7eeaa 100644 --- a/src/DataContainer/EntityImportQuickConfigContainer.php +++ b/src/DataContainer/EntityImportQuickConfigContainer.php @@ -92,6 +92,10 @@ public function getDryRunOperation($row, $href, $label, $title, $icon, $attribut public function modifyDca(DataContainer $dc) { + if (!$dc->id) { + return; + } + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { return; } @@ -255,6 +259,10 @@ public function prepareCachedCsvRows($items, $config, $options = [], $context = public function getParentEntitiesAsOptions(DataContainer $dc): array { + if (!$dc->id) { + return []; + } + if (null === ($quickImporter = $this->utils->model()->findModelInstanceByPk('tl_entity_import_quick_config', $dc->id)) || !$quickImporter->importerConfig) { return []; }