New command vitec:markets-figma-elements (dry-run by default, --apply writes) for the 11 main market pages: numbered placeholder image placeholders/pages/<uid>-02.png and learnmore_target /contact on vitec_marketintro, the hidden Trusted-by and Case-studies shells of the 2026-10-23 page import unhidden (logos with the home page settings, case studies as testimonials with the empty selection = all stories, like the sub-market pages), the lead paragraph in the hero slot hidden (decision aehm 12.11., not deleted). Sub-market pages: vitec_marketinsights CTA /success-stories -> /insights/news with the label Explore our insights. Applied on DEV 2026-11-12: 11 images, 11 targets, 11 logo bands, 11 sliders, 11 hidden leads, 24 CTAs, 0 warnings; protocol in migrations/markets_figma_apply_20261112.txt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
511 lines
27 KiB
PHP
511 lines
27 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evomedien\Vitec\Command;
|
|
|
|
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;
|
|
use TYPO3\CMS\Core\Database\Connection;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
|
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
|
use TYPO3\CMS\Core\Resource\StorageRepository;
|
|
use TYPO3\CMS\Core\Service\FlexFormService;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* Ticket 0124-FE (2026-11-12): Joe aligned the market pages with Figma, the
|
|
* front end renders the new bands (0118-FE) - this command creates the
|
|
* content they need. Per main market page (/markets/<slug>, 11 pages):
|
|
*
|
|
* B1 vitec_marketintro: numbered placeholder image placeholders/pages/<uid>-02.png
|
|
* when the element has no image yet; learnmore_target "/contact" when empty.
|
|
* B2 vitec_customerlogos ("Trusted by"): the 2026-10-23 page import left a
|
|
* HIDDEN shell after the sub-market cards - it is unhidden and gets the
|
|
* settings of the home page element (#758: layout, customer selection).
|
|
* A page without a shell gets a copy of #758 instead.
|
|
* B3 vitec_usecaselist ("Case studies" shell, hidden as well): unhidden as
|
|
* layout testimonials. Stories = what an editor already picked on the
|
|
* shell, else the union of the market's sub-market testimonial lists,
|
|
* else the most common sub-market list. No shell: a new element after
|
|
* the logos, i.e. before the CTA banner.
|
|
* B5 the lead paragraph in the hero slot (vitec_introparagraph, colPos 1)
|
|
* is HIDDEN, not deleted (decision aehm 12.11.: copy raw material for Joe).
|
|
*
|
|
* Sub-market pages (children of the 11):
|
|
* B4 vitec_marketinsights: cta_target /success-stories -> /insights/news,
|
|
* label "Explore success stories" -> "Explore our insights" (placeholder).
|
|
*
|
|
* Idempotent: elements that already exist are left alone, filled fields are
|
|
* never overwritten. Everything goes through DataHandler (history, refindex,
|
|
* SSR auto-purge), still run cache:flush + vitec:ssr-purge/-warm afterwards -
|
|
* the command prints the exact lines.
|
|
*
|
|
* vendor/bin/typo3 vitec:markets-figma-elements # dry run (default)
|
|
* vendor/bin/typo3 vitec:markets-figma-elements --apply # writes
|
|
*/
|
|
#[AsCommand(
|
|
name: 'vitec:markets-figma-elements',
|
|
description: 'Create the Figma structure elements on the 11 main market pages (0124-FE, dry-run by default)'
|
|
)]
|
|
final class MarketsFigmaElementsCommand extends Command
|
|
{
|
|
private const MARKET_SLUGS = [
|
|
'/markets/broadcast-and-telecoms',
|
|
'/markets/corporation',
|
|
'/markets/critical-national-infrastructure',
|
|
'/markets/defense-and-security',
|
|
'/markets/education',
|
|
'/markets/government',
|
|
'/markets/hospitality',
|
|
'/markets/industrial',
|
|
'/markets/public-safety',
|
|
'/markets/sports-and-entertainment',
|
|
'/markets/transport',
|
|
];
|
|
private const LOGO_TEMPLATE_UID = 758;
|
|
private const STORY_SINGLE_PID = 26;
|
|
private const CONTACT_TARGET = '/contact';
|
|
private const LEARNMORE_LABEL = 'Talk to our team';
|
|
private const OLD_INSIGHTS_TARGET = '/success-stories';
|
|
private const NEW_INSIGHTS_TARGET = '/insights/news';
|
|
private const OLD_INSIGHTS_LABEL = 'Explore success stories';
|
|
private const NEW_INSIGHTS_LABEL = 'Explore our insights';
|
|
private const TESTIMONIALS_HEADER = 'Testimonials from the Field';
|
|
private const GENERIC_PLACEHOLDER = 'placeholders/placeholder-grey.png';
|
|
|
|
private Connection $db;
|
|
private ResourceStorage $storage;
|
|
private OutputInterface $output;
|
|
private bool $apply = false;
|
|
|
|
/** @var array<string,int> */
|
|
private array $counts = [
|
|
'images' => 0, 'targets' => 0, 'labels' => 0, 'logos' => 0, 'testimonials' => 0,
|
|
'hiddenLeads' => 0, 'insightsTargets' => 0, 'insightsLabels' => 0, 'warnings' => 0,
|
|
];
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this->addOption('apply', null, InputOption::VALUE_NONE, 'Write the changes (default: dry run)');
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
Bootstrap::initializeBackendAuthentication();
|
|
$this->output = $output;
|
|
$this->apply = (bool)$input->getOption('apply');
|
|
$this->db = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tt_content');
|
|
$this->storage = GeneralUtility::makeInstance(StorageRepository::class)->getDefaultStorage();
|
|
|
|
$output->writeln($this->apply ? '<comment>APPLY - writing via DataHandler</comment>' : 'DRY RUN - nothing is written (--apply to write)');
|
|
$output->writeln('');
|
|
|
|
// --- template element for the logo band ------------------------------------
|
|
$template = $this->db->fetchAssociative(
|
|
'SELECT uid, pid, CType, header, hidden, deleted FROM tt_content WHERE uid = ?',
|
|
[self::LOGO_TEMPLATE_UID]
|
|
);
|
|
if (!$template || $template['CType'] !== 'vitec_customerlogos' || (int)$template['deleted'] === 1) {
|
|
$output->writeln(sprintf('<error>Logo template #%d is not a live vitec_customerlogos element.</error>', self::LOGO_TEMPLATE_UID));
|
|
return Command::FAILURE;
|
|
}
|
|
$templateSettings = $this->flexSettings((string)$this->db->fetchOne('SELECT pi_flexform FROM tt_content WHERE uid = ?', [self::LOGO_TEMPLATE_UID]));
|
|
$output->writeln(sprintf(
|
|
'Logo template: #%d "%s" (page %d) - layout %s, customers %s, onlySelected %s',
|
|
$template['uid'], $template['header'], $template['pid'],
|
|
$templateSettings['layout'] ?? '-', ($templateSettings['customers'] ?? '') !== '' ? $templateSettings['customers'] : '(all)', $templateSettings['onlySelected'] ?? '0'
|
|
));
|
|
|
|
// --- the 11 market pages -------------------------------------------------------
|
|
$markets = [];
|
|
foreach (self::MARKET_SLUGS as $slug) {
|
|
$page = $this->db->fetchAssociative(
|
|
'SELECT uid, title, slug FROM pages WHERE deleted = 0 AND sys_language_uid = 0 AND slug = ?',
|
|
[$slug]
|
|
);
|
|
if (!$page) {
|
|
$this->warn("page $slug NOT FOUND - skipped");
|
|
continue;
|
|
}
|
|
$markets[(int)$page['uid']] = $page;
|
|
}
|
|
if ($markets === []) {
|
|
return Command::FAILURE;
|
|
}
|
|
$marketUids = array_keys($markets);
|
|
$in = implode(',', array_map('intval', $marketUids));
|
|
|
|
// --- sub-market testimonial lists (story selection for B3) ----------------------
|
|
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
|
$storiesByMarket = [];
|
|
$listFrequency = [];
|
|
$rows = $this->db->fetchAllAssociative(
|
|
"SELECT c.uid, c.pid, p.pid AS market, c.pi_flexform FROM tt_content c
|
|
JOIN pages p ON p.uid = c.pid
|
|
WHERE c.CType = 'vitec_usecaselist' AND c.deleted = 0 AND c.hidden = 0 AND c.sys_language_uid = 0
|
|
AND p.deleted = 0 AND p.pid IN ($in)
|
|
ORDER BY p.sorting, c.sorting"
|
|
);
|
|
foreach ($rows as $row) {
|
|
$settings = $this->flexSettings((string)$row['pi_flexform']);
|
|
$uids = array_values(array_filter(array_map('intval', explode(',', (string)($settings['usecases'] ?? '')))));
|
|
if ($output->isVerbose()) {
|
|
$output->writeln(sprintf(' list #%d (page %d, market %d): layout=%s usecases=%d', $row['uid'], $row['pid'], $row['market'], $settings['layout'] ?? '-', count($uids)));
|
|
}
|
|
if (($settings['layout'] ?? '') !== 'testimonials' || $uids === []) {
|
|
continue;
|
|
}
|
|
$market = (int)$row['market'];
|
|
foreach ($uids as $uid) {
|
|
if (!in_array($uid, $storiesByMarket[$market] ?? [], true)) {
|
|
$storiesByMarket[$market][] = $uid;
|
|
}
|
|
}
|
|
$key = implode(',', $uids);
|
|
$listFrequency[$key] = ($listFrequency[$key] ?? 0) + 1;
|
|
}
|
|
arsort($listFrequency);
|
|
$defaultStories = $listFrequency === [] ? [] : array_map('intval', explode(',', (string)array_key_first($listFrequency)));
|
|
$output->writeln(sprintf(
|
|
'Sub-market testimonial lists: %d element(s) on %d market(s); default list (most common, %dx): %s',
|
|
count($rows), count($storiesByMarket), $listFrequency === [] ? 0 : reset($listFrequency), implode(',', $defaultStories)
|
|
));
|
|
$output->writeln('');
|
|
|
|
$purgePaths = [];
|
|
|
|
// =====================================================================
|
|
// main market pages
|
|
// =====================================================================
|
|
foreach ($markets as $pid => $page) {
|
|
$output->writeln(sprintf('<info>== %s (pid %d) "%s"</info>', $page['slug'], $pid, $page['title']));
|
|
$purgePaths[] = (string)$page['slug'];
|
|
|
|
$elements = $this->db->fetchAllAssociative(
|
|
'SELECT uid, CType, colPos, sorting, hidden, header, tx_container_parent,
|
|
vitec_learnmore_label, vitec_learnmore_target
|
|
FROM tt_content
|
|
WHERE pid = ? AND deleted = 0 AND sys_language_uid = 0
|
|
ORDER BY colPos, sorting, uid',
|
|
[$pid]
|
|
);
|
|
foreach ($elements as $el) {
|
|
$output->writeln(sprintf(
|
|
' col %-3d %5d #%-6d %-22s %s%s',
|
|
$el['colPos'], $el['sorting'], $el['uid'], $el['CType'],
|
|
(int)$el['hidden'] === 1 ? '[hidden] ' : '',
|
|
$this->short((string)$el['header'])
|
|
));
|
|
}
|
|
$topLevel = array_values(array_filter($elements, static fn(array $e): bool => (int)$e['tx_container_parent'] === 0));
|
|
$live = array_values(array_filter($topLevel, static fn(array $e): bool => (int)$e['hidden'] === 0));
|
|
$col0 = array_values(array_filter($live, static fn(array $e): bool => (int)$e['colPos'] === 0));
|
|
$col1 = array_values(array_filter($live, static fn(array $e): bool => (int)$e['colPos'] === 1));
|
|
$col0All = array_values(array_filter($topLevel, static fn(array $e): bool => (int)$e['colPos'] === 0));
|
|
|
|
$datamap = ['tt_content' => [], 'sys_file_reference' => []];
|
|
|
|
// --- B1: intro image + learn-more target --------------------------------
|
|
$intro = $this->first($col0, 'vitec_marketintro');
|
|
if ($intro === null) {
|
|
$this->warn('no live vitec_marketintro in colPos 0 - B1 skipped');
|
|
} else {
|
|
$introUid = (int)$intro['uid'];
|
|
$refs = (int)$this->db->fetchOne(
|
|
"SELECT COUNT(*) FROM sys_file_reference WHERE deleted = 0 AND tablenames = 'tt_content' AND fieldname = 'vitec_image' AND uid_foreign = ?",
|
|
[$introUid]
|
|
);
|
|
if ($refs > 0) {
|
|
$output->writeln(sprintf(' B1 intro #%d already has %d image(s) - unchanged', $introUid, $refs));
|
|
} else {
|
|
$identifier = 'placeholders/pages/' . $pid . '-02.png';
|
|
if (!$this->storage->hasFile($identifier)) {
|
|
$this->warn("$identifier missing - using generic " . self::GENERIC_PLACEHOLDER);
|
|
$identifier = self::GENERIC_PLACEHOLDER;
|
|
}
|
|
if (!$this->storage->hasFile($identifier)) {
|
|
$this->warn("$identifier missing as well - no image attached");
|
|
} else {
|
|
$file = $this->storage->getFile($identifier);
|
|
$refId = 'NEWimg' . $pid;
|
|
$datamap['sys_file_reference'][$refId] = [
|
|
'pid' => $pid,
|
|
'table_local' => 'sys_file',
|
|
'uid_local' => $file->getUid(),
|
|
'tablenames' => 'tt_content',
|
|
'fieldname' => 'vitec_image',
|
|
'uid_foreign' => $introUid,
|
|
];
|
|
$datamap['tt_content'][$introUid]['vitec_image'] = $refId;
|
|
$output->writeln(sprintf(' B1 intro #%d: attach %s (sys_file %d)', $introUid, $identifier, $file->getUid()));
|
|
$this->counts['images']++;
|
|
}
|
|
}
|
|
if (trim((string)$intro['vitec_learnmore_target']) === '') {
|
|
$datamap['tt_content'][$introUid]['vitec_learnmore_target'] = self::CONTACT_TARGET;
|
|
$output->writeln(sprintf(' B1 intro #%d: learnmore_target "" -> %s', $introUid, self::CONTACT_TARGET));
|
|
$this->counts['targets']++;
|
|
if (trim((string)$intro['vitec_learnmore_label']) === '') {
|
|
$datamap['tt_content'][$introUid]['vitec_learnmore_label'] = self::LEARNMORE_LABEL;
|
|
$output->writeln(sprintf(' B1 intro #%d: learnmore_label "" -> %s', $introUid, self::LEARNMORE_LABEL));
|
|
$this->counts['labels']++;
|
|
}
|
|
} else {
|
|
$output->writeln(sprintf(' B1 intro #%d: learnmore_target already "%s" - unchanged', $introUid, $intro['vitec_learnmore_target']));
|
|
}
|
|
}
|
|
|
|
// --- B5: hide the lead paragraph in the hero slot --------------------------
|
|
$leads = array_values(array_filter($col1, static fn(array $e): bool => $e['CType'] === 'vitec_introparagraph'));
|
|
if (count($leads) !== 1) {
|
|
$this->warn(sprintf('%d live vitec_introparagraph in colPos 1 (expected 1)%s', count($leads), $leads === [] ? ' - B5 skipped' : ' - hiding all of them'));
|
|
}
|
|
foreach ($leads as $lead) {
|
|
$datamap['tt_content'][(int)$lead['uid']]['hidden'] = 1;
|
|
$output->writeln(sprintf(' B5 lead #%d "%s": hide', $lead['uid'], $this->short((string)$lead['header'])));
|
|
$this->counts['hiddenLeads']++;
|
|
}
|
|
|
|
// --- anchor: last sub-market card after the "Explore" intro paragraph -----
|
|
$anchorUid = 0;
|
|
$anchorLabel = '';
|
|
$exploreIdx = null;
|
|
foreach ($col0 as $i => $el) {
|
|
if ($el['CType'] === 'vitec_introparagraph') {
|
|
$exploreIdx = $i;
|
|
break;
|
|
}
|
|
}
|
|
if ($exploreIdx !== null) {
|
|
$anchorUid = (int)$col0[$exploreIdx]['uid'];
|
|
$anchorLabel = 'intro paragraph #' . $anchorUid;
|
|
for ($i = $exploreIdx + 1; $i < count($col0) && $col0[$i]['CType'] === 'vitec_card'; $i++) {
|
|
$anchorUid = (int)$col0[$i]['uid'];
|
|
$anchorLabel = 'card #' . $anchorUid . ' "' . $this->short((string)$col0[$i]['header']) . '"';
|
|
}
|
|
} elseif ($intro !== null) {
|
|
$anchorUid = (int)$intro['uid'];
|
|
$anchorLabel = 'market intro #' . $anchorUid;
|
|
}
|
|
|
|
// the 2026-10-23 page import left HIDDEN shells for both bands - reuse them
|
|
$logosShell = $this->first($col0All, 'vitec_customerlogos');
|
|
$testiShell = $this->first($col0All, 'vitec_usecaselist');
|
|
$hasLogos = $logosShell !== null && (int)$logosShell['hidden'] === 0;
|
|
$hasTesti = $testiShell !== null && (int)$testiShell['hidden'] === 0;
|
|
$hasCta = $this->first($col0, 'vitec_ctabanner') !== null;
|
|
if (!$hasCta) {
|
|
$this->warn('no vitec_ctabanner in colPos 0 - the new bands will end the page');
|
|
}
|
|
|
|
// --- B2 (shell): unhide the "Trusted by" shell with the home page settings -----
|
|
if ($logosShell !== null && !$hasLogos) {
|
|
$logosUid = (int)$logosShell['uid'];
|
|
$datamap['tt_content'][$logosUid] = [
|
|
'hidden' => 0,
|
|
'pi_flexform' => ['data' => ['sDEF' => ['lDEF' => [
|
|
'settings.layout' => ['vDEF' => (string)($templateSettings['layout'] ?? 'marquee')],
|
|
'settings.customers' => ['vDEF' => (string)($templateSettings['customers'] ?? '')],
|
|
'settings.onlySelected' => ['vDEF' => (string)($templateSettings['onlySelected'] ?? '0')],
|
|
]]]],
|
|
];
|
|
$output->writeln(sprintf(' B2 unhide logo shell #%d "%s", settings as #%d', $logosUid, $this->short((string)$logosShell['header']), self::LOGO_TEMPLATE_UID));
|
|
$this->counts['logos']++;
|
|
}
|
|
|
|
// --- B3: testimonials -------------------------------------------------------------
|
|
// Selection: explicit lists of the market's sub-markets when they have
|
|
// any; today all 24 sub-market sliders have an EMPTY selection, which
|
|
// the plugin renders as "all stories" - the main pages do the same.
|
|
$stories = $storiesByMarket[$pid] ?? [];
|
|
$storySource = 'own sub-markets';
|
|
if ($stories === []) {
|
|
$stories = $defaultStories;
|
|
$storySource = 'most common sub-market list';
|
|
}
|
|
if ($stories === []) {
|
|
$storySource = 'empty selection = all stories, like the sub-market pages';
|
|
}
|
|
if ($hasTesti) {
|
|
$output->writeln(' B3 vitec_usecaselist already on the page - unchanged');
|
|
} elseif ($testiShell !== null) {
|
|
$testiUid = (int)$testiShell['uid'];
|
|
$current = $this->flexSettings((string)$this->db->fetchOne('SELECT pi_flexform FROM tt_content WHERE uid = ?', [$testiUid]));
|
|
$currentStories = array_values(array_filter(array_map('intval', explode(',', (string)($current['usecases'] ?? '')))));
|
|
if ($currentStories !== []) {
|
|
$stories = $currentStories;
|
|
$storySource = 'already selected on the shell';
|
|
}
|
|
{
|
|
$datamap['tt_content'][$testiUid] = [
|
|
'hidden' => 0,
|
|
'pi_flexform' => ['data' => ['sDEF' => ['lDEF' => [
|
|
'settings.layout' => ['vDEF' => 'testimonials'],
|
|
'settings.usecases' => ['vDEF' => implode(',', $stories)],
|
|
'settings.singlePid' => ['vDEF' => (string)self::STORY_SINGLE_PID],
|
|
'settings.showtoolbar' => ['vDEF' => '0'],
|
|
]]]],
|
|
];
|
|
$output->writeln(sprintf(' B3 unhide shell #%d "%s" as testimonials (%s%s)', $testiUid, $this->short((string)$testiShell['header']), $storySource, $stories === [] ? '' : ': ' . implode(',', $stories)));
|
|
$this->counts['testimonials']++;
|
|
}
|
|
} elseif ($anchorUid === 0) {
|
|
$this->warn('no anchor element found - B2/B3 skipped');
|
|
} else {
|
|
$datamap['tt_content']['NEWtesti' . $pid] = [
|
|
'pid' => -$anchorUid,
|
|
'colPos' => 0,
|
|
'sys_language_uid' => 0,
|
|
'CType' => 'vitec_usecaselist',
|
|
'header' => self::TESTIMONIALS_HEADER,
|
|
'pi_flexform' => [
|
|
'data' => ['sDEF' => ['lDEF' => [
|
|
'settings.layout' => ['vDEF' => 'testimonials'],
|
|
'settings.usecases' => ['vDEF' => implode(',', $stories)],
|
|
'settings.singlePid' => ['vDEF' => (string)self::STORY_SINGLE_PID],
|
|
'settings.showtoolbar' => ['vDEF' => '0'],
|
|
]]],
|
|
],
|
|
];
|
|
$output->writeln(sprintf(' B3 new vitec_usecaselist (testimonials, %s%s) after %s', $storySource, $stories === [] ? '' : ': ' . implode(',', $stories), $anchorLabel));
|
|
$this->counts['testimonials']++;
|
|
}
|
|
|
|
// --- write datamap ------------------------------------------------------------
|
|
$this->runDataHandler($datamap, []);
|
|
|
|
// --- B2 (no shell): logo band copied from #758, after the anchor (= before testimonials)
|
|
if ($hasLogos) {
|
|
$output->writeln(' B2 vitec_customerlogos already visible on the page - unchanged');
|
|
} elseif ($logosShell !== null) {
|
|
// handled above (shell unhidden in the datamap)
|
|
} elseif ($anchorUid > 0) {
|
|
$cmdmap = ['tt_content' => [self::LOGO_TEMPLATE_UID => ['copy' => [
|
|
'action' => 'paste',
|
|
'target' => -$anchorUid,
|
|
'update' => ['colPos' => 0],
|
|
]]]];
|
|
$output->writeln(sprintf(' B2 copy logo band #%d after %s', self::LOGO_TEMPLATE_UID, $anchorLabel));
|
|
$this->counts['logos']++;
|
|
$this->runDataHandler([], $cmdmap);
|
|
}
|
|
$output->writeln('');
|
|
}
|
|
|
|
// =====================================================================
|
|
// sub-market pages: insights CTA -> news
|
|
// =====================================================================
|
|
$output->writeln('<info>== sub-market pages: vitec_marketinsights CTA</info>');
|
|
$insights = $this->db->fetchAllAssociative(
|
|
"SELECT c.uid, c.pid, c.hidden, c.vitec_cta_label, c.vitec_cta_target, p.slug, p.hidden AS page_hidden
|
|
FROM tt_content c JOIN pages p ON p.uid = c.pid
|
|
WHERE c.CType = 'vitec_marketinsights' AND c.deleted = 0 AND c.sys_language_uid = 0
|
|
AND p.deleted = 0 AND p.pid IN ($in)
|
|
ORDER BY p.slug"
|
|
);
|
|
$datamap = ['tt_content' => []];
|
|
foreach ($insights as $row) {
|
|
$uid = (int)$row['uid'];
|
|
$target = trim((string)$row['vitec_cta_target']);
|
|
$label = trim((string)$row['vitec_cta_label']);
|
|
$changes = [];
|
|
if ($target === self::OLD_INSIGHTS_TARGET || $target === '') {
|
|
$datamap['tt_content'][$uid]['vitec_cta_target'] = self::NEW_INSIGHTS_TARGET;
|
|
$changes[] = sprintf('target "%s" -> %s', $target, self::NEW_INSIGHTS_TARGET);
|
|
$this->counts['insightsTargets']++;
|
|
if ($label === self::OLD_INSIGHTS_LABEL || $label === '') {
|
|
$datamap['tt_content'][$uid]['vitec_cta_label'] = self::NEW_INSIGHTS_LABEL;
|
|
$changes[] = sprintf('label "%s" -> %s', $label, self::NEW_INSIGHTS_LABEL);
|
|
$this->counts['insightsLabels']++;
|
|
} else {
|
|
$changes[] = sprintf('label "%s" kept (not the standard text)', $label);
|
|
}
|
|
$purgePaths[] = (string)$row['slug'];
|
|
} elseif ($target === self::NEW_INSIGHTS_TARGET) {
|
|
$changes[] = 'already ' . self::NEW_INSIGHTS_TARGET;
|
|
} else {
|
|
$changes[] = sprintf('target "%s" is individual - unchanged', $target);
|
|
}
|
|
$output->writeln(sprintf(' #%-6d %-60s %s%s', $uid, $row['slug'], (int)$row['hidden'] === 1 || (int)$row['page_hidden'] === 1 ? '[hidden] ' : '', implode('; ', $changes)));
|
|
}
|
|
$this->runDataHandler($datamap, []);
|
|
$output->writeln('');
|
|
|
|
// =====================================================================
|
|
$output->writeln(sprintf(
|
|
'%s: %d intro image(s), %d learnmore target(s) (+%d label), %d logo band(s), %d testimonial slider(s), %d lead paragraph(s) hidden, %d insights target(s) (+%d label); %d warning(s).',
|
|
$this->apply ? 'DONE' : 'DRY RUN - would write',
|
|
$this->counts['images'], $this->counts['targets'], $this->counts['labels'], $this->counts['logos'], $this->counts['testimonials'],
|
|
$this->counts['hiddenLeads'], $this->counts['insightsTargets'], $this->counts['insightsLabels'], $this->counts['warnings']
|
|
));
|
|
$purgePaths = array_values(array_unique($purgePaths));
|
|
$output->writeln('Afterwards: vendor/bin/typo3 cache:flush && vendor/bin/typo3 vitec:ssr-purge ' . implode(' ', $purgePaths) . ' && vendor/bin/typo3 vitec:ssr-warm ' . implode(' ', $purgePaths));
|
|
return Command::SUCCESS;
|
|
}
|
|
|
|
/** @param array<string,array<string|int,array<string,mixed>>> $datamap */
|
|
private function runDataHandler(array $datamap, array $cmdmap): void
|
|
{
|
|
$datamap = array_filter($datamap);
|
|
if (!$this->apply || ($datamap === [] && $cmdmap === [])) {
|
|
return;
|
|
}
|
|
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
|
$dataHandler->start($datamap, $cmdmap);
|
|
if ($datamap !== []) {
|
|
$dataHandler->process_datamap();
|
|
}
|
|
if ($cmdmap !== []) {
|
|
$dataHandler->process_cmdmap();
|
|
}
|
|
if ($dataHandler->errorLog !== []) {
|
|
$this->warn('DataHandler: ' . implode(' | ', $dataHandler->errorLog));
|
|
}
|
|
foreach ($dataHandler->substNEWwithIDs as $new => $uid) {
|
|
$this->output->writeln(sprintf(' written: %s -> #%d', $new, $uid));
|
|
}
|
|
foreach ($dataHandler->copyMappingArray_merged['tt_content'] ?? [] as $from => $to) {
|
|
$this->output->writeln(sprintf(' copied: #%d -> #%d', $from, $to));
|
|
}
|
|
}
|
|
|
|
/** @return array<string,string> flat "settings" sheet of a pi_flexform XML (empty when none) */
|
|
private function flexSettings(string $xml): array
|
|
{
|
|
if (trim($xml) === '') {
|
|
return [];
|
|
}
|
|
$settings = GeneralUtility::makeInstance(FlexFormService::class)->convertFlexFormContentToArray($xml)['settings'] ?? [];
|
|
return is_array($settings) ? array_map(static fn($v): string => is_scalar($v) ? (string)$v : '', $settings) : [];
|
|
}
|
|
|
|
/** @param array<int,array<string,mixed>> $elements */
|
|
private function first(array $elements, string $cType): ?array
|
|
{
|
|
foreach ($elements as $el) {
|
|
if ($el['CType'] === $cType) {
|
|
return $el;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function warn(string $message): void
|
|
{
|
|
$this->counts['warnings']++;
|
|
$this->output->writeln(' <comment>WARN ' . $message . '</comment>');
|
|
}
|
|
|
|
private function short(string $text): string
|
|
{
|
|
$text = trim((string)preg_replace('/\s+/', ' ', strip_tags($text)));
|
|
return mb_strlen($text) > 60 ? mb_substr($text, 0, 57) . '...' : $text;
|
|
}
|
|
}
|