diff --git a/Classes/Command/GlossaryCsvImportCommand.php b/Classes/Command/GlossaryCsvImportCommand.php new file mode 100644 index 0000000..e6a3408 --- /dev/null +++ b/Classes/Command/GlossaryCsvImportCommand.php @@ -0,0 +1,84 @@ +importGlossaryEntryService = $importGlossaryEntryService; + parent::__construct($name); + } + + protected function configure(): void + { + $this->addOption( + 'pageId', + 'p', + InputOption::VALUE_REQUIRED, + 'Page to import to', + null + ) + ->addOption( + 'csvFilePath', + 'f', + InputOption::VALUE_REQUIRED, + 'combined file identifier [[storage uid]:] e.g. 1:/user_upload/csv_imports', + null + ) + ->addOption( + 'csvSeparator', + 's', + InputOption::VALUE_OPTIONAL, + 'csv seperator default: ,', + ',' + ) + ->addOption( + 'targetSysLanguage', + 't', + InputOption::VALUE_REQUIRED, + 'The target language sys_language_uid', + null + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $io->title('Glossary Entry CSV Import'); + + try { + $glossaryEntries = $this->importGlossaryEntryService->getGlossaryEntriesFromCsv( + (string) $input->getOption('csvFilePath'), + (string) $input->getOption('csvSeparator') + ); + + $this->importGlossaryEntryService->insertEntriesLocal( + $glossaryEntries, + (int) $input->getOption('pageId'), + (int) $input->getOption('targetSysLanguage') + ); + + $this->deeplGlossaryService->syncGlossaries((int)$input->getOption('pageId')); + } catch (Throwable $e) { + $io->error($e->getMessage()); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/Classes/Domain/Repository/GlossaryEntryRepository.php b/Classes/Domain/Repository/GlossaryEntryRepository.php index 8ddc77d..378db31 100644 --- a/Classes/Domain/Repository/GlossaryEntryRepository.php +++ b/Classes/Domain/Repository/GlossaryEntryRepository.php @@ -4,12 +4,21 @@ namespace WebVision\WvDeepltranslate\Domain\Repository; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; -use TYPO3\CMS\Core\Utility\GeneralUtility; // @todo Consider to rename/move this as service class. final class GlossaryEntryRepository { + public const TABLE_NAME = 'tx_wvdeepltranslate_glossaryentry'; + + protected ?Connection $connection = null; + + public function __construct(ConnectionPool $connectionPool) + { + $this->connection = $connectionPool->getConnectionForTable(self::TABLE_NAME); + } + /** * @deprecated */ @@ -25,12 +34,9 @@ public function hasEntriesForGlossary(int $parentId): bool */ public function findEntriesByGlossary(int $parentId): array { - $connection = GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable('tx_wvdeepltranslate_glossaryentry'); - - $result = $connection->select( + $result = $this->connection->select( ['*'], - 'tx_wvdeepltranslate_glossaryentry', + self::TABLE_NAME, [ 'glossary' => $parentId, ] @@ -44,12 +50,9 @@ public function findEntriesByGlossary(int $parentId): array */ public function findEntryByUid(int $uid): array { - $connection = GeneralUtility::makeInstance(ConnectionPool::class) - ->getConnectionForTable('tx_wvdeepltranslate_glossaryentry'); - - $result = $connection->select( + $result = $this->connection->select( ['*'], - 'tx_wvdeepltranslate_glossaryentry', + self::TABLE_NAME, [ 'uid' => $uid, ] @@ -58,4 +61,11 @@ public function findEntryByUid(int $uid): array // @todo Should we not better returning null instead of an empty array if nor recourd could be retrieved ? return $result->fetchAssociative() ?: []; } + + public function add(array $entry): int + { + $this->connection->insert(self::TABLE_NAME, $entry); + + return (int) $this->connection->lastInsertId(self::TABLE_NAME); + } } diff --git a/Classes/Exception/FileNotFoundException.php b/Classes/Exception/FileNotFoundException.php new file mode 100644 index 0000000..d342ea8 --- /dev/null +++ b/Classes/Exception/FileNotFoundException.php @@ -0,0 +1,11 @@ +resourceFactory = $resourceFactory; + $this->glossaryEntryRepository = $glossaryEntryRepository; + } + + /** + * @throws FileNotFoundException + */ + public function getGlossaryEntriesFromCsv(string $filePath, string $separator): array + { + $file = $this->getFile($filePath); + + if ($file === null) { + throw new FileNotFoundException('Csv file not found identifier: ' . $filePath, 1729245627); + } + + return CsvUtility::csvToArray($file->getContents(), $separator, '"', 2); + } + + public function insertEntriesLocal(array $entries, int $pageId, int $sysLanguageUid): void + { + foreach ($entries as $entry) { + $originalUid = $this->glossaryEntryRepository->add([ + 'pid' => $pageId, + 'term' => $entry[0], + ]); + + if ($originalUid > 0) { + $this->glossaryEntryRepository->add([ + 'l10n_parent' => $originalUid, + 'sys_language_uid' => $sysLanguageUid, + 'pid' => $pageId, + 'term' => $entry[1], + ]); + } + } + } + + protected function getFile(string $csvFilePath): ?File + { + return $this->resourceFactory->getFileObjectFromCombinedIdentifier($csvFilePath); + } +} diff --git a/Configuration/Services.php b/Configuration/Services.php index 81fe737..698ed09 100644 --- a/Configuration/Services.php +++ b/Configuration/Services.php @@ -14,6 +14,7 @@ use WebVision\WvDeepltranslate\Client; use WebVision\WvDeepltranslate\ClientInterface; use WebVision\WvDeepltranslate\Command\GlossaryCleanupCommand; +use WebVision\WvDeepltranslate\Command\GlossaryCsvImportCommand; use WebVision\WvDeepltranslate\Command\GlossaryListCommand; use WebVision\WvDeepltranslate\Command\GlossarySyncCommand; use WebVision\WvDeepltranslate\Controller\Backend\AjaxController; @@ -77,6 +78,16 @@ 'schedulable' => false, ] ); + $services + ->set(GlossaryCsvImportCommand::class) + ->tag( + 'console.command', + [ + 'command' => 'deepl:glossary:import', + 'description' => 'Import glossary entries from a csv file', + 'schedulable' => true, + ] + ); // add caching $services->set('cache.wvdeepltranslate')