diff --git a/packages/vitec/Classes/Command/ImportMarketsMainPageCommand.php b/packages/vitec/Classes/Command/ImportMarketsMainPageCommand.php new file mode 100644 index 0000000..13ebdc8 --- /dev/null +++ b/packages/vitec/Classes/Command/ImportMarketsMainPageCommand.php @@ -0,0 +1,285 @@ + 'Create connected guest experiences with IPTV, digital signage and video distribution that support entertainment, communications and consistent service across hospitality environments.', + 'Transport' => 'Keep passengers informed and operations visible with real-time information, video distribution and visualization across airports, stations and complex transport environments.', + 'Sports & Entertainment' => 'Connect fans, guests and operators with live video, digital signage, data visualization and control-room technology across venues, events and entertainment environments.', + 'Broadcast & Telecoms' => 'Support contribution, production, distribution and network operations with reliable video workflows designed for demanding broadcast, media and telecom environments.', + 'Education' => 'Deliver live and on-demand video, campus communications and digital information across learning spaces, shared facilities and student accommodation.', + 'Corporation' => 'Improve communication and operational awareness across workplaces with IPTV, digital signage, video distribution, dashboards and control-room visualization.', + 'Industrial' => 'Give industrial teams clearer oversight of sites, processes and operational data through centralized monitoring, visualization and secure video distribution.', + 'Critical National Infrastructure' => 'Support resilient infrastructure operations with real-time monitoring, secure communications and visualization that help teams maintain awareness across critical systems and assets.', + 'Government' => 'Enable secure communications, public information delivery and coordinated operations across government environments with reliable video, signage and control-room solutions.', + 'Public Safety' => 'Bring live video, operational data and incident information into a shared view to support situational awareness, coordination and faster response.', + 'Defense & Security' => 'Deliver secure, low-latency video and mission-critical visualization for ISR, command and control, tactical distribution and high-security operational environments.', + ]; + + private const PREFOOTER_H2 = 'Find the right approach for your environment'; + private const PREFOOTER_COPY = 'Your operational priorities determine the technology you need. Talk to VITEC about your environment, workflows and users, and we can help identify the video, visualization and communications approach that fits your requirements.'; + private const PREFOOTER_PRIMARY = 'Talk to our team'; + private const PREFOOTER_SECONDARY = 'Explore solutions'; + + private const SEO_TITLE = 'Video Solutions by Market | VITEC'; + private const SEO_DESCRIPTION = 'Explore VITEC video, visualization, streaming and communications solutions for transport, broadcast, industry, government, public safety and more.'; + /** Curated page keywords (Claude, 2026-10-26), same convention as the hero pages. */ + private const KEYWORDS = 'video solutions by market, IPTV, digital signage, video distribution, visualization, control room, broadcast, transport, government, public safety, defense, critical infrastructure'; + + protected function configure(): void + { + $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'); + $pool = GeneralUtility::makeInstance(ConnectionPool::class); + $pages = $pool->getConnectionForTable('pages'); + $content = $pool->getConnectionForTable('tt_content'); + + $marketsUid = $this->pageUid($pages, '/markets'); + $solutionsUid = $this->pageUid($pages, '/solutions'); + $contactUid = $this->pageUid($pages, '/contact'); + if (!$marketsUid || !$solutionsUid || !$contactUid) { + $output->writeln(sprintf('Page lookup failed: /markets=%d /solutions=%d /contact=%d', $marketsUid, $solutionsUid, $contactUid)); + return Command::FAILURE; + } + $output->writeln(($apply ? 'APPLY' : 'DRY-RUN') . sprintf(' · /markets #%d · /solutions #%d · /contact #%d', $marketsUid, $solutionsUid, $contactUid)); + $datamap = []; + + // --- resolve the existing elements --------------------------------------- + $hero = $this->one($content, $marketsUid, "CType = 'vitec_herosection' AND colPos = 1", 'hero'); + $intro = $this->one($content, $marketsUid, "CType = 'vitec_introparagraph' AND colPos = 0 AND tx_container_parent = 0", 'markets intro'); + $containers = $content->fetchAllAssociative( + "SELECT uid, hidden, sorting FROM tt_content WHERE deleted = 0 AND sys_language_uid IN (0,-1) AND pid = ? AND CType = 'vitec_container' AND colPos = 0 AND tx_container_parent = 0 ORDER BY sorting", + [$marketsUid] + ); + if ($hero === null || $intro === null || count($containers) !== 2) { + $output->writeln('Unexpected page structure (' . count($containers) . ' top-level containers) - aborting.'); + return Command::FAILURE; + } + $overviewContainer = $prefooterContainer = null; + $overviewHeader = $marketlist = null; + foreach ($containers as $container) { + $children = $content->fetchAllAssociative( + 'SELECT uid, CType, header, hidden, sorting FROM tt_content WHERE deleted = 0 AND tx_container_parent = ? ORDER BY sorting', + [(int)$container['uid']] + ); + foreach ($children as $child) { + if ($child['CType'] === 'vitec_marketlist') { + $overviewContainer = $container; + $marketlist = $child; + } + if ($child['CType'] === 'header') { + $overviewHeader = $child; + } + } + if ($overviewContainer === null || (int)$container['uid'] !== (int)$overviewContainer['uid']) { + $prefooterContainer = $container; + } + } + if ($overviewContainer === null || $prefooterContainer === null || $overviewHeader === null || $marketlist === null) { + $output->writeln('Could not identify overview/prefooter containers - aborting.'); + return Command::FAILURE; + } + + // --- hero ----------------------------------------------------------------- + $datamap['tt_content'][(int)$hero['uid']] = [ + 'header' => self::HERO_H1, + 'bodytext' => $this->p(self::HERO_COPY), + 'vitec_cta_label' => self::HERO_CTA, + ]; + $output->writeln(sprintf(' ~ hero #%d: H1 "%s" + copy + CTA label "%s" (anchor target kept)', (int)$hero['uid'], self::HERO_H1, self::HERO_CTA)); + + // --- markets intro 50/50 ---------------------------------------------------- + $datamap['tt_content'][(int)$intro['uid']] = [ + 'header' => self::INTRO_H2, + 'bodytext' => $this->p(self::INTRO_COPY), + 'vitec_button_text' => self::INTRO_CTA, + 'vitec_button_link' => 't3://page?uid=' . $solutionsUid, + ]; + $output->writeln(sprintf(' ~ intro #%d: H2 "%s" + copy + CTA "%s" -> /solutions', (int)$intro['uid'], self::INTRO_H2, self::INTRO_CTA)); + + // --- overview header + marketlist selection -------------------------------- + $datamap['tt_content'][(int)$overviewHeader['uid']] = ['header' => self::OVERVIEW_H2]; + $output->writeln(sprintf(' ~ overview header #%d: "%s" -> "%s"', (int)$overviewHeader['uid'], (string)$overviewHeader['header'], self::OVERVIEW_H2)); + + $markets = $pool->getConnectionForTable('tx_vitec_domain_model_market')->fetchAllAssociative( + 'SELECT uid, title, teaser FROM tx_vitec_domain_model_market WHERE deleted = 0 AND sys_language_uid IN (0,-1)' + ); + $selection = []; + $missing = []; + foreach (self::CARDS as $title => $body) { + $record = null; + foreach ($markets as $row) { + if ($this->norm((string)$row['title']) === $this->norm($title)) { + $record = $row; + break; + } + } + if ($record === null) { + $missing[] = $title; + continue; + } + $selection[] = (int)$record['uid']; + $oldTeaser = trim((string)$record['teaser']); + if ($oldTeaser !== $body) { + $datamap['tx_vitec_domain_model_market'][(int)$record['uid']] = ['teaser' => $body]; + $output->writeln(sprintf(' ~ market record #%d "%s": teaser %s', (int)$record['uid'], (string)$record['title'], + $oldTeaser === '' ? 'set (was empty)' : 'OVERWRITTEN ("' . mb_substr($oldTeaser, 0, 50) . '...")')); + } else { + $output->writeln(sprintf(' = market record #%d "%s": teaser already current', (int)$record['uid'], (string)$record['title'])); + } + } + if ($missing !== []) { + $output->writeln('No market record for: ' . implode(', ', $missing) . ' - aborting.'); + return Command::FAILURE; + } + $datamap['tt_content'][(int)$marketlist['uid']]['pi_flexform'] = [ + 'data' => ['sDEF' => ['lDEF' => ['settings.markets' => ['vDEF' => implode(',', $selection)]]]], + ]; + $output->writeln(sprintf(' ~ marketlist #%d: selection -> 11 records in workbook order (%s)', (int)$marketlist['uid'], implode(',', $selection))); + + // --- prefooter: hide the old container, add the two-CTA banner ------------- + if ((int)$prefooterContainer['hidden'] !== 1) { + $datamap['tt_content'][(int)$prefooterContainer['uid']]['hidden'] = 1; + $output->writeln(sprintf(' ~ old prefooter container #%d: HIDDEN (kept for reference)', (int)$prefooterContainer['uid'])); + } + $datamap['tt_content']['NEWPREFOOTER'] = [ + 'pid' => $marketsUid, + 'CType' => 'vitec_ctabanner', + 'colPos' => 0, + 'header' => self::PREFOOTER_H2, + 'bodytext' => $this->p(self::PREFOOTER_COPY), + 'vitec_primary_cta' => 't3://page?uid=' . $contactUid, + 'vitec_primary_cta_label' => self::PREFOOTER_PRIMARY, + 'vitec_secondary_cta' => 't3://page?uid=' . $solutionsUid, + 'vitec_secondary_cta_label' => self::PREFOOTER_SECONDARY, + 'vitec_layout_variant' => 'centered', + 'vitec_background_variant' => 'orange', + ]; + $output->writeln(sprintf(' + prefooter vitec_ctabanner (centered/orange, as on the hero pages): "%s" -> /contact, "%s" -> /solutions', + self::PREFOOTER_PRIMARY, self::PREFOOTER_SECONDARY)); + + // --- page properties -------------------------------------------------------- + $datamap['pages'][$marketsUid] = [ + 'seo_title' => self::SEO_TITLE, + 'og_title' => self::SEO_TITLE, + 'twitter_title' => self::SEO_TITLE, + 'description' => self::SEO_DESCRIPTION, + 'og_description' => self::SEO_DESCRIPTION, + 'twitter_description' => self::SEO_DESCRIPTION, + 'abstract' => self::SEO_DESCRIPTION, + 'canonical_link' => 't3://page?uid=' . $marketsUid, + 'keywords' => self::KEYWORDS, + ]; + $output->writeln(' ~ page meta: seo/og/twitter title + description, canonical, abstract, keywords'); + + if (!$apply) { + $output->writeln("\nDRY RUN - nothing written. Add --apply to write."); + return Command::SUCCESS; + } + + $dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $dataHandler->start($datamap, []); + $dataHandler->process_datamap(); + if ($dataHandler->errorLog !== []) { + $output->writeln('DataHandler: ' . implode(' | ', $dataHandler->errorLog) . ''); + return Command::FAILURE; + } + // DataHandler puts NEW records at the top - move the banner to the end, + // right where the hidden old prefooter container sits. + $newUid = (int)($dataHandler->substNEWwithIDs['NEWPREFOOTER'] ?? 0); + if ($newUid > 0) { + $content->update('tt_content', ['sorting' => (int)$prefooterContainer['sorting'] + 8], ['uid' => $newUid]); + $output->writeln(sprintf('Done. Banner #%d sorted after the old container. Flush the frontend cache.', $newUid)); + } else { + $output->writeln('New prefooter banner uid not resolved.'); + return Command::FAILURE; + } + return Command::SUCCESS; + } + + private function pageUid($pages, string $slug): int + { + $row = $pages->fetchAssociative('SELECT uid FROM pages WHERE deleted = 0 AND slug = ? AND sys_language_uid IN (0,-1)', [$slug]); + return (int)($row['uid'] ?? 0); + } + + /** @return array|null exactly one matching element, else null (reported) */ + private function one($content, int $pid, string $where, string $label): ?array + { + $rows = $content->fetchAllAssociative( + 'SELECT uid, header, hidden, sorting FROM tt_content WHERE deleted = 0 AND sys_language_uid IN (0,-1) AND pid = ? AND ' . $where, + [$pid] + ); + return count($rows) === 1 ? $rows[0] : null; + } + + private function p(string $text): string + { + return '

' . htmlspecialchars($text, ENT_QUOTES | ENT_HTML5) . '

'; + } + + private function norm(string $value): string + { + return (string)preg_replace('/[^a-z0-9]+/', '', str_replace(['+', '&'], ['plus', 'and'], mb_strtolower($value))); + } +}