Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add command to import glossary entries #368

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions Classes/Command/GlossaryCsvImportCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

declare(strict_types=1);

namespace WebVision\WvDeepltranslate\Command;

use Symfony\Component\Console\Command\Command;
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 Throwable;
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
use TYPO3\CMS\Core\Core\Bootstrap;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use WebVision\WvDeepltranslate\Service\ImportGlossaryEntryService;

class GlossaryCsvImportCommand extends Command
{
use GlossaryCommandTrait;

protected ImportGlossaryEntryService $importGlossaryEntryService;

public function __construct(ImportGlossaryEntryService $importGlossaryEntryService, ?string $name = null)
{
$this->importGlossaryEntryService = $importGlossaryEntryService;
parent::__construct($name);
}

protected function initialize(InputInterface $input, OutputInterface $output): void
{
Bootstrap::initializeBackendAuthentication();
}

protected function configure(): void
{
$this->addOption(
'pageId',
'p',
InputOption::VALUE_REQUIRED,
'Page to import to',
null
)
->addOption(
'csvFilePath',
'f',
InputOption::VALUE_REQUIRED,
'The path to the csv file',
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');

$pageId = (int) $input->getOption('pageId');
$sysLanguageUid = (int) $input->getOption('targetSysLanguage');

if ($this->translationExistsForPage($pageId, $sysLanguageUid) === false) {
$io->error(
'Page with uid: "' . $pageId . '" has no translation for sys_language_uid: "' . $sysLanguageUid .
'". Create a translation for this page first'
);

return Command::FAILURE;
}

try {
$glossaryEntries = $this->importGlossaryEntryService->getGlossaryEntriesFromCsv(
(string) $input->getOption('csvFilePath'),
(string) $input->getOption('csvSeparator'),
$pageId
);

$this->importGlossaryEntryService->insertEntriesLocal($glossaryEntries, $pageId, $sysLanguageUid);
$this->outputFailures($io);

$this->deeplGlossaryService->syncGlossaries($pageId);
} catch (Throwable $e) {
$io->error($e->getMessage());
return Command::FAILURE;
}

return Command::SUCCESS;
}

protected function outputFailures(SymfonyStyle $symfonyStyle): void
{
$allEntriesCount = count($this->importGlossaryEntryService->getAllEntries());
$successCount = $allEntriesCount - $this->importGlossaryEntryService->getFailuresCount();

foreach ($this->importGlossaryEntryService->getDataHandlerErrors() as $dataHandlerError) {
$symfonyStyle->error('Error while inserting data:' . $dataHandlerError);
}

foreach (
$this->importGlossaryEntryService->getFailedEntries(ImportGlossaryEntryService::ERROR_INVALID)
as $csvColumn => $entry
) {
$symfonyStyle->error('Invalid csv entry ' . $entry . 'in line: ' . $csvColumn);
}

foreach (
$this->importGlossaryEntryService->getFailedEntries(ImportGlossaryEntryService::ERROR_EXISTING)
as $column => $entry
) {
$symfonyStyle->info('Skipping entry ' . $entry . ' as it already exists. Line: ' . $column);
}

foreach (
$this->importGlossaryEntryService->getFailedEntries(ImportGlossaryEntryService::ERROR_LOCALIZATION)
as $entry
) {
$symfonyStyle->error('Failed to localize entry: ' . $entry);
}

$symfonyStyle->info('Imported ' . $successCount . '/' . $allEntriesCount . ' entries.');
}

protected function translationExistsForPage(int $pageId, int $sysLanguageId): bool
{
$translations = GeneralUtility::makeInstance(TranslationConfigurationProvider::class)
->translationInfo('pages', $pageId);

if (is_array($translations) === false) {
return false;
}

foreach ($translations['translations'] as $translation) {
if ((int)($translation['sys_language_uid'] ?? 0) === $sysLanguageId) {
return true;
}
}

return false;
}
}
30 changes: 21 additions & 9 deletions Classes/Domain/Repository/GlossaryEntryRepository.php
Original file line number Diff line number Diff line change
@@ -4,12 +4,19 @@

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
{
protected Connection $connection;

public function __construct(ConnectionPool $connectionPool)
{
$this->connection = $connectionPool->getConnectionForTable('tx_wvdeepltranslate_glossaryentry');
}

/**
* @deprecated
*/
@@ -25,10 +32,7 @@ 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',
[
@@ -44,10 +48,7 @@ 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',
[
@@ -58,4 +59,15 @@ 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 findBy(array $identifiers): ?array
{
$result = $this->connection->select(
['*'],
'tx_wvdeepltranslate_glossaryentry',
$identifiers
)->fetchAssociative();

return is_array($result) ? $result : null;
}
}
11 changes: 11 additions & 0 deletions Classes/Exception/FileNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace WebVision\WvDeepltranslate\Exception;

use Exception;

class FileNotFoundException extends Exception
{
}
Loading