Files
VITEC-website/packages/vitec/Classes/Command/ImportMarketHeroWorkbooksCommand.php
orascheandClaude Fable 5 303defe24d Markets branch: 11-market tree, sub-market import, hero pipeline
Structure per the agency agreement (Joe/Christina, confirmed 2026-10-23):
/markets -> eleven market hero pages -> their sub-market pages; the
use-case level stays sections on the sub-market pages.

- new vitec:align-market-pages: renames 8 main market pages onto the
  agency names (slug change -> EXT:redirects), creates the 3 new markets,
  hides the dropped "Security & Surveillance" (records/stories
  untouched), reports unknown children. A child-slug pass realigns every
  child to <parent-slug>/<slugified-title> (DataHandler does not cascade
  slug changes; this also repaired the third embedded-slash slug,
  rtcc-/-rtic). --publish unhides the tree (dev convenience; element
  shells stay hidden)
- sub-market import: all 24 agency workbooks applied (4 existing pages,
  20 created) - their Meta sheets were clones of Corporate Environments
  (record_uid 32) and were repaired beforehand (market/sub-market set,
  record_uid 0; the record taxonomy alignment is a separate step)
- SubmarketWorkbookImporter: page-properties parity (og/twitter titles
  and descriptions, canonical, abstract from the intro copy) and a
  placeholder upgrade pass - pages created in the same run only get the
  generic grey (their uid is still NEW), the re-run swaps it for the
  numbered per-page placeholder
- new market hero pipeline: frontend layout 23 "Market Hero Page"
  (zones hero=colPos1, main=colPos0), Import\MarketHeroPageImporter +
  vitec:import-market-hero-workbooks (inbox migrations/markethero_xlsx_in,
  gitignored). Hero H1 travels on the introparagraph header (markethero
  has no headline field), the hero CTA anchors #submarkets, sub-market
  cards link their child page via t3://page, customerlogos and
  usecaselist ship hidden until filled editorially, prefooter ctabanner;
  full page-properties set with 11 curated keyword sets
- applied end to end and live-verified (layout 23 heroes with linked
  cards, layout 21 sub pages with all six colPos groups, clean slugs)
- spec v1.25

Open: market RECORD taxonomy (story badges / Solr facets still use the
old 9 mains), featured product "ISR Video Mission Commander" has no
record, the /markets main page workbook (template 2) is a separate flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-23 13:52:43 +02:00

91 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Command;
use Evomedien\Vitec\Import\MarketHeroPageImporter;
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 MarketHeroPageImporter - imports the agency "Markets
* hero sub page" workbooks into the eleven market hero pages (layout 23).
* Run vitec:align-market-pages first, and ideally the sub-market import
* before this one so the sub-market cards can link their child pages.
*
* vendor/bin/typo3 vitec:import-market-hero-workbooks
* vendor/bin/typo3 vitec:import-market-hero-workbooks --only=hospitality --apply
*/
#[AsCommand(
name: 'vitec:import-market-hero-workbooks',
description: 'Import the agency market hero workbooks into the market pages (dry-run by default)'
)]
final class ImportMarketHeroWorkbooksCommand extends Command
{
public function __construct(private readonly MarketHeroPageImporter $importer)
{
parent::__construct();
}
protected function configure(): void
{
$this->addOption('dir', null, InputOption::VALUE_REQUIRED, 'Directory with filled workbooks', 'migrations/markethero_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> (market: %s)", $result['file'], $result['market']));
foreach ($result['report'] as $line) {
$output->writeln(' <comment>' . $line . '</comment>');
}
$output->writeln(sprintf(' page #%d · %d element(s) touched · %s',
$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;
}
}