- Extract the complete import logic (label-scan reading, page find-or-create, slot filling, content-idempotent image attach) from ImportSubmarketWorkbooksCommand into the shared service SubmarketWorkbookImporter; the CLI command and the new controller are thin shells around importFile(), so both entry points cannot drift. CLI regression verified: identical dry-run report for the reference workbook (12 elements, page 443) - New "Sub-Markets" tab in the VITEC Import module (SubmarketImportController + Import/Submarkets.html): upload multiple filled workbooks into the SAME inbox the CLI uses (migrations/submarkets_xlsx_in/), per-file checkboxes, dry-run and apply buttons, result cards with the familiar report and a direct link into the page module; backend dry-run verified against the CLI - Submitted filenames are whitelisted against the actual directory listing (no path traversal); uploads restricted to sanitized .xlsx basenames - Routes submarkets/submarket_upload/submarket_process in Modules.php, Sub-Markets tab link in the sibling module views, ok/danger badge colors in backend-import.css - Docs section 5y (incl. operations note: apply in small batches in the module, mass runs via CLI - image downloads run inside the web request)
92 lines
3.8 KiB
PHP
92 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evomedien\Vitec\Command;
|
|
|
|
use Evomedien\Vitec\Import\SubmarketWorkbookImporter;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
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 TYPO3\CMS\Core\Core\Bootstrap;
|
|
|
|
/**
|
|
* CLI shell around SubmarketWorkbookImporter (the shared implementation also
|
|
* used by the "VITEC Import" backend module since 2026-10-05 - behavior and
|
|
* lessons are documented THERE).
|
|
*
|
|
* vendor/bin/typo3 vitec:import-submarket-workbooks --dir=migrations/submarkets_xlsx_in
|
|
* vendor/bin/typo3 vitec:import-submarket-workbooks --dir=... --only=broadcast --apply
|
|
*/
|
|
#[AsCommand(
|
|
name: 'vitec:import-submarket-workbooks',
|
|
description: 'Import filled sub-market workbooks into layout-21 pages (dry-run by default)'
|
|
)]
|
|
final class ImportSubmarketWorkbooksCommand extends Command
|
|
{
|
|
public function __construct(private readonly SubmarketWorkbookImporter $importer)
|
|
{
|
|
parent::__construct();
|
|
}
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this->addOption('dir', null, InputOption::VALUE_REQUIRED, 'Directory with filled workbooks', 'migrations/submarkets_xlsx_in');
|
|
$this->addOption('only', null, InputOption::VALUE_REQUIRED, 'Only files whose name contains this substring');
|
|
$this->addOption('apply', null, InputOption::VALUE_NONE, 'Write changes (default: dry run)');
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
Bootstrap::initializeBackendAuthentication();
|
|
$apply = (bool)$input->getOption('apply');
|
|
$only = mb_strtolower((string)($input->getOption('only') ?? ''));
|
|
$dir = rtrim((string)$input->getOption('dir'), '/');
|
|
|
|
if (!is_dir($dir)) {
|
|
$output->writeln('<error>Directory not found: ' . $dir . '</error>');
|
|
return Command::FAILURE;
|
|
}
|
|
$files = [];
|
|
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
|
|
foreach ($iterator as $fileInfo) {
|
|
$name = $fileInfo->getFilename();
|
|
if ($fileInfo->isFile() && str_ends_with($name, '.xlsx') && !str_starts_with($name, '~$')) {
|
|
if ($only === '' || str_contains(mb_strtolower($name), $only)) {
|
|
$files[] = $fileInfo->getPathname();
|
|
}
|
|
}
|
|
}
|
|
sort($files);
|
|
if ($files === []) {
|
|
$output->writeln('No workbooks found in ' . $dir);
|
|
return Command::SUCCESS;
|
|
}
|
|
$output->writeln(count($files) . ' workbook(s) · ' . ($apply ? 'APPLY' : 'DRY-RUN'));
|
|
|
|
$errors = 0;
|
|
foreach ($files as $file) {
|
|
try {
|
|
$result = $this->importer->importFile($file, $apply);
|
|
$output->writeln(sprintf("\n<info>%s</info> (%s / %s)", $result['file'], $result['group'], $result['submarket']));
|
|
foreach ($result['report'] as $line) {
|
|
$output->writeln(' <comment>' . $line . '</comment>');
|
|
}
|
|
$output->writeln(sprintf(' page %s · %d element(s) touched · %s',
|
|
$result['newPage'] && $result['pageUid'] === 0 ? 'NEW' : '#' . $result['pageUid'],
|
|
$result['elements'],
|
|
$apply ? 'written.' : 'dry run'));
|
|
} catch (\Throwable $e) {
|
|
$errors++;
|
|
$output->writeln('<error>' . basename($file) . ': ' . $e->getMessage() . '</error>');
|
|
}
|
|
}
|
|
if (!$apply) {
|
|
$output->writeln("\nDRY RUN - nothing written. Add --apply to write.");
|
|
}
|
|
return $errors > 0 ? Command::FAILURE : Command::SUCCESS;
|
|
}
|
|
}
|