Files
VITEC-website/packages/vitec/Classes/Command/ImportSubsolutionWorkbooksCommand.php
orasche 8e67f5db23 Sub-solution workbook pipeline on layout 5, fix element order in both importers
- New, deliberately separate pipeline for sub-solutions (markets and
  solutions must never break each other): generator
  vitec:generate-subsolution-workbooks (list from the page tree below
  /solutions, 39 workbooks, every sub-solution matched to its record),
  SubsolutionWorkbookImporter + CLI vitec:import-subsolution-workbooks,
  and a "Sub-Solutions" tab in the VITEC Import module with its own
  inbox migrations/subsolutions_xlsx_in/
- Targets the existing template 5 "Solution Detail" without changing
  it: hero element in colPos 1, the remaining sections in fixed order in
  colPos 0, found again by CType on re-import; existing empty sub-solution
  pages are switched to layout 5, missing ones created hidden;
  detail_page set on the solution record
- Workbook per the Figma "07 Solution Detail page": hero without video
  teaser, intro with headline and without learn-more/portfolio, extended
  icon list, own page-layout reference image
- Shared content elements: market hero/intro/insights relabelled
  "Market/Solution", market intro gains the optional intro_headline
  field (new column, requires extension:setup)
- Fix: DataHandler puts every NEW record with a positive pid at the top
  of its column, so pages built in one run came out reversed. Both
  importers now track the page order per colPos and write
  tt_content.sorting explicitly after the DataHandler run; re-applying
  repairs pages imported earlier (verified on the control-room
  sub-solution and sub-market page 443)
- migrations/fill_test_subsolution.php: realistic test content from
  vitec.com copy and images for the control-room sub-solution
- Docs section 5z
2026-10-07 15:39:18 +02:00

92 lines
3.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Command;
use Evomedien\Vitec\Import\SubsolutionWorkbookImporter;
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 SubsolutionWorkbookImporter - the deliberate sibling of
* vitec:import-submarket-workbooks (markets and solutions evolve
* independently; behavior and lessons are documented on the importer).
*
* vendor/bin/typo3 vitec:import-subsolution-workbooks --dir=migrations/subsolutions_xlsx_in
* vendor/bin/typo3 vitec:import-subsolution-workbooks --dir=... --only=iptv --apply
*/
#[AsCommand(
name: 'vitec:import-subsolution-workbooks',
description: 'Import filled sub-solution workbooks into layout-5 pages (dry-run by default)'
)]
final class ImportSubsolutionWorkbooksCommand extends Command
{
public function __construct(private readonly SubsolutionWorkbookImporter $importer)
{
parent::__construct();
}
protected function configure(): void
{
$this->addOption('dir', null, InputOption::VALUE_REQUIRED, 'Directory with filled workbooks', 'migrations/subsolutions_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['subsolution']));
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;
}
}