usecase base + seo_* * mask_web__markets_card -> subtitle, card_image, customer_logo * mask_web__success_story__promo -> hero (bgimage, overlay color, theme) * use_case element (APP) -> industries (sys_category) -> markets MM * page content (flux tree) -> inline content_elements: * text -> text CE (blockquote quotes -> vitec_quotation) * image/textmedia -> image/textmedia CE + FAL * html -> html CE * 2-column flux rows -> vitec_columns content block * * Usage: * vendor/bin/typo3 vitec:import-success-stories --dry-run * vendor/bin/typo3 vitec:import-success-stories --only=meydan-racecourse * vendor/bin/typo3 vitec:import-success-stories --force (reimport existing) */ #[AsCommand( name: 'vitec:import-success-stories', description: 'Import success stories from the old-website export JSON into the usecase model' )] final class ImportSuccessStoriesCommand extends Command { private const TABLE = 'tx_vitec_domain_model_usecase'; private const FILES_BASE = '/success_stories_import'; /** Old market2 token -> canonical industry title. */ private const INDUSTRY_TOKENS = [ 'sports' => 'Sports', 'venues' => 'Venues', 'government' => 'Government', 'military' => 'Military', 'corporate' => 'Corporate', 'broadcast' => 'Broadcast', 'education' => 'Education', 'healthcare' => 'Healthcare', 'hospitality' => 'Hospitality & Leisure', 'leisure' => 'Hospitality & Leisure', 'accomodation' => 'Accommodation', 'accommodation' => 'Accommodation', ]; /** CTypes that never become content elements. */ private const SKIP_CTYPES = [ 'shortcut', 'div', 'fluxbs5templates_buttonlink', 'mask_web__jumpmenu', 'mask_web__markets_card', 'mask_web__success_story__full_width_promo', 'mask_web__success_top_story', 'mask_use_case', 'header', ]; private const CONTAINER_CTYPES = ['fluxbs5templates_fluidrow', 'fluxbs5templates_container']; private array $export = []; private array $pagesByUid = []; private array $contentByUid = []; private array $childrenByParent = []; // parentCeUid => list of child CEs private array $rootsByPid = []; // pageUid => list of colPos-0/5 CEs private array $cardByPageUid = []; private array $useCaseByKey = []; private array $fileRefs = []; // "table:uid:field" => refs[] private array $catTitleByUid = []; private array $catsByCeUid = []; private array $marketUidByTitle = []; private array $fileUidCache = []; private string $collectionTable = ''; protected function configure(): void { $this->addOption('file', null, InputOption::VALUE_REQUIRED, 'Path to success_stories_export.json', 'var/transient/success_stories_export.json'); $this->addOption('pid', null, InputOption::VALUE_REQUIRED, 'Storage pid for new usecase records (default: pid of existing records)'); $this->addOption('only', null, InputOption::VALUE_REQUIRED, 'Import only the story whose slug tail matches'); $this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Analyse and report only, write nothing'); $this->addOption('force', null, InputOption::VALUE_NONE, 'Delete + recreate stories that already exist (matched by slug)'); } protected function execute(InputInterface $input, OutputInterface $output): int { Bootstrap::initializeBackendAuthentication(); $file = (string)$input->getOption('file'); if (!is_file($file)) { $output->writeln("Export file not found: $file"); return Command::FAILURE; } $this->export = json_decode((string)file_get_contents($file), true) ?? []; if (!isset($this->export['pages'])) { $output->writeln('Export JSON malformed (no pages key)'); return Command::FAILURE; } $dryRun = (bool)$input->getOption('dry-run'); $force = (bool)$input->getOption('force'); $only = (string)($input->getOption('only') ?? ''); $this->buildIndexes(); $storagePid = $this->resolveStoragePid($input->getOption('pid')); if ($storagePid <= 0) { $output->writeln('No storage pid — pass --pid or create one usecase record manually first.'); return Command::FAILURE; } $this->collectionTable = (string)($GLOBALS['TCA']['tt_content']['columns']['vitec_items']['config']['foreign_table'] ?? ''); $rootUids = array_map(static fn(array $r): int => (int)$r['uid'], $this->export['storyRoots']); $stories = array_values(array_filter( $this->export['pages'], static fn(array $p): bool => (int)($p['sys_language_uid'] ?? 0) === 0 && in_array((int)$p['pid'], $rootUids, true) )); $output->writeln(sprintf( '%d stories in export · storage pid %d · %s', count($stories), $storagePid, $dryRun ? 'DRY-RUN' : 'LIVE' )); if (!$dryRun) { $this->ensureMarkets($output, $storagePid); } $summary = []; foreach ($stories as $page) { $slugTail = basename((string)$page['slug']); if ($only !== '' && $slugTail !== $only) { continue; } try { $summary[] = $this->importStory($page, $storagePid, $dryRun, $force, $output); } catch (\Throwable $e) { $summary[] = [$slugTail, 'ERROR: ' . $e->getMessage(), 0, 0, 0]; $output->writeln(" $slugTail: {$e->getMessage()}"); } } $output->writeln(''); $output->writeln(str_pad('story', 55) . str_pad('action', 12) . str_pad('CEs', 5) . str_pad('imgs', 6) . 'markets'); foreach ($summary as [$slug, $action, $ces, $imgs, $mk]) { $output->writeln(str_pad(substr($slug, 0, 53), 55) . str_pad($action, 12) . str_pad((string)$ces, 5) . str_pad((string)$imgs, 6) . $mk); } return Command::SUCCESS; } // =========================================================== index building private function buildIndexes(): void { foreach ($this->export['pages'] as $p) { $this->pagesByUid[(int)$p['uid']] = $p; } $allCes = array_merge($this->export['content'], $this->export['storyElementsElsewhere']); foreach ($allCes as $c) { $this->contentByUid[(int)$c['uid']] = $c; } // flux tree: colPos >= 100 encodes parentUid*100 + column foreach ($this->export['content'] as $c) { $colPos = (int)($c['colPos'] ?? 0); if ($colPos >= 100 && isset($this->contentByUid[intdiv($colPos, 100)])) { $this->childrenByParent[intdiv($colPos, 100)][] = $c; } else { $this->rootsByPid[(int)$c['pid']][] = $c; } } foreach ($this->childrenByParent as &$list) { usort($list, static fn($a, $b) => [($a['colPos'] % 100), $a['sorting']] <=> [($b['colPos'] % 100), $b['sorting']]); } unset($list); foreach ($this->rootsByPid as &$list) { usort($list, static fn($a, $b) => $a['sorting'] <=> $b['sorting']); } unset($list); // story card by target page uid (tx_mask_web__markets_card_link = "427" | "t3://page?uid=427") foreach ($allCes as $c) { if (($c['CType'] ?? '') !== 'mask_web__markets_card') { continue; } $link = (string)($c['tx_mask_web__markets_card_link'] ?? ''); if (preg_match('/uid=(\d+)/', $link, $m)) { $target = (int)$m[1]; } elseif (ctype_digit(trim($link))) { $target = (int)trim($link); } else { continue; } // prefer the newest non-hidden card per target $known = $this->cardByPageUid[$target] ?? null; if ($known === null || ((int)$c['tstamp'] > (int)$known['tstamp'] && empty($c['hidden']))) { $this->cardByPageUid[$target] = $c; } } // use_case (APP) elements by normalised title foreach ($allCes as $c) { if (($c['CType'] ?? '') !== 'mask_use_case') { continue; } foreach ([(string)($c['header'] ?? ''), (string)($c['tx_mask_tile_customer'] ?? '')] as $t) { $k = $this->normTitle($t); if ($k !== '' && !isset($this->useCaseByKey[$k])) { $this->useCaseByKey[$k] = $c; } } } foreach ($this->export['fileReferences'] as $r) { if (!empty($r['hidden'])) { continue; } $this->fileRefs[$r['tablenames'] . ':' . $r['uid_foreign'] . ':' . $r['fieldname']][] = $r; } foreach ($this->export['categories'] as $c) { $this->catTitleByUid[(int)$c['uid']] = (string)$c['title']; } foreach ($this->export['categoryAssignments'] as $a) { if ($a['tablenames'] === 'tt_content') { $this->catsByCeUid[(int)$a['uid_foreign']][] = $this->catTitleByUid[(int)$a['category_uid']] ?? ''; } } } /** Resolve a content-blocks column name: prefixed variant if it exists in TCA. */ private function cbCol(string $table, string $name): string { return isset($GLOBALS['TCA'][$table]['columns']['vitec_' . $name]) ? 'vitec_' . $name : $name; } private function normTitle(string $t): string { return preg_replace('/[^a-z0-9]/', '', strtolower($t)) ?? ''; } private function refs(string $table, int $uid, string $field): array { return $this->fileRefs[$table . ':' . $uid . ':' . $field] ?? []; } // ============================================================== market setup private function resolveStoragePid(mixed $option): int { if ($option !== null && (int)$option > 0) { return (int)$option; } $row = GeneralUtility::makeInstance(ConnectionPool::class) ->getConnectionForTable(self::TABLE) ->select(['pid'], self::TABLE, ['deleted' => 0], [], [], 1) ->fetchAssociative(); return (int)($row['pid'] ?? 0); } /** Ensure one market record per canonical industry; fill title->uid map. */ private function ensureMarkets(OutputInterface $output, int $fallbackPid): void { $conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_vitec_domain_model_market'); $rows = $conn->select(['uid', 'title', 'pid'], 'tx_vitec_domain_model_market', ['deleted' => 0])->fetchAllAssociative(); $marketPid = $fallbackPid; foreach ($rows as $r) { $this->marketUidByTitle[$this->normTitle((string)$r['title'])] = (int)$r['uid']; $marketPid = (int)$r['pid']; } $canonical = array_unique(array_values(self::INDUSTRY_TOKENS)); $datamap = []; foreach ($canonical as $i => $title) { if (!isset($this->marketUidByTitle[$this->normTitle($title)])) { $datamap['tx_vitec_domain_model_market']['NEWMARKET' . $i] = ['pid' => $marketPid, 'title' => $title]; } } if ($datamap !== []) { $dh = GeneralUtility::makeInstance(DataHandler::class); $dh->start($datamap, []); $dh->process_datamap(); foreach ($datamap['tx_vitec_domain_model_market'] as $key => $fields) { $uid = (int)($dh->substNEWwithIDs[$key] ?? 0); if ($uid > 0) { $this->marketUidByTitle[$this->normTitle($fields['title'])] = $uid; $output->writeln(" created market: {$fields['title']} ($uid)"); } } } } /** @return string[] canonical industry titles for one story */ private function industriesForStory(array $page, ?array $card, ?array $useCase): array { $out = []; if ($useCase !== null) { foreach ($this->catsByCeUid[(int)$useCase['uid']] ?? [] as $t) { if ($t !== '') { $out[] = $t; } } } if ($out === [] && $card !== null) { $tokens = preg_split('/\s+/', strtolower((string)($card['tx_mask_markets_card_market2'] ?? ''))) ?: []; foreach ($tokens as $tok) { if (isset(self::INDUSTRY_TOKENS[$tok])) { $out[] = self::INDUSTRY_TOKENS[$tok]; } } } return array_values(array_unique($out)); } // =============================================================== story import /** @return array{0:string,1:string,2:int,3:int,4:string} */ private function importStory(array $page, int $storagePid, bool $dryRun, bool $force, OutputInterface $output): array { $pageUid = (int)$page['uid']; $slugTail = basename((string)$page['slug']); $slug = '/' . $slugTail; $card = $this->cardByPageUid[$pageUid] ?? null; $useCase = $this->useCaseByKey[$this->normTitle((string)$page['title'])] ?? null; $promo = null; foreach ($this->rootsByPid[$pageUid] ?? [] as $ce) { if ($ce['CType'] === 'mask_web__success_story__full_width_promo') { $promo = $ce; break; } } $industries = $this->industriesForStory($page, $card, $useCase); $ceSpecs = $this->transformContent($pageUid); // ---- existing? $conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable(self::TABLE); $existing = $conn->select(['uid'], self::TABLE, ['slug' => $slug, 'deleted' => 0])->fetchAssociative() ?: $conn->select(['uid'], self::TABLE, ['slug' => $slugTail, 'deleted' => 0])->fetchAssociative(); $imgCount = 0; foreach ($ceSpecs as $s) { $imgCount += count($s['files'] ?? []); foreach ($s['items'] ?? [] as $it) { $imgCount += count($it['files'] ?? []); } } if ($dryRun) { $output->writeln(sprintf( ' %s ces=%d quotes=%d cols=%d imgs=%d markets=[%s] card=%s uc=%s hero=%s%s', $slugTail, count($ceSpecs), count(array_filter($ceSpecs, fn($s) => $s['CType'] === 'vitec_quotation')), count(array_filter($ceSpecs, fn($s) => $s['CType'] === 'vitec_columns')), $imgCount, implode(',', $industries), $card ? 'y' : 'MISSING', $useCase ? 'y' : '-', $promo ? 'y' : 'MISSING', $existing ? ' EXISTS(uid=' . $existing['uid'] . ')' : '' )); return [$slugTail, $existing ? 'dry(exists)' : 'dry', count($ceSpecs), $imgCount, implode(',', $industries)]; } if ($existing) { if (!$force) { return [$slugTail, 'skipped', 0, 0, '']; } $dh = GeneralUtility::makeInstance(DataHandler::class); $dh->start([], [self::TABLE => [(int)$existing['uid'] => ['delete' => 1]]]); $dh->process_cmdmap(); } // ---- assemble datamap $datamap = []; $storyKey = 'NEWSTORY1'; $refCounter = 0; $newRef = function (string $identifier, string $table, string $key, string $field, array $meta = []) use (&$datamap, &$refCounter, $storagePid): ?string { $fileUid = $this->fileUidFor($identifier); if ($fileUid === null) { return null; } $refKey = 'NEWREF' . (++$refCounter); $datamap['sys_file_reference'][$refKey] = array_filter([ 'pid' => $storagePid, 'uid_local' => 'sys_file_' . $fileUid, 'tablenames' => $table, 'uid_foreign' => $key, 'fieldname' => $field, 'title' => $meta['title'] ?? null, 'alternative' => $meta['alternative'] ?? null, 'description' => $meta['description'] ?? null, 'crop' => $meta['crop'] ?? null, ], static fn($v) => $v !== null && $v !== ''); return $refKey; }; $attach = function (array $refs, string $table, string $key, string $newField) use ($newRef): string { $ids = []; foreach ($refs as $r) { $id = $newRef((string)$r['identifier'], $table, $key, $newField, $r); if ($id !== null) { $ids[] = $id; } } return implode(',', $ids); }; // story base fields $subtitle = trim(strip_tags((string)($card['tx_mask_web__markets_card_frontttext'] ?? ''))); if ($subtitle === '' && $useCase !== null) { $subtitle = (string)($useCase['tx_mask_tile_teaser'] ?? ''); } $textColor = strtolower((string)($promo['tx_mask_web__success_story__full_width_promo_textcolor'] ?? '')); $overlay = (string)($promo['tx_mask_web__success_story__full_width_promo_color'] ?? ''); $story = [ 'pid' => $storagePid, 'title' => (string)$page['title'], 'slug' => $slug, 'subtitle' => $subtitle, 'teaser' => (string)($page['abstract'] ?? '') !== '' ? (string)$page['abstract'] : (string)($page['description'] ?? ''), 'seo_title' => (string)($page['seo_title'] ?? ''), 'seo_description' => (string)($page['description'] ?? ''), 'og_title' => (string)($page['og_title'] ?? ''), 'og_description' => (string)($page['og_description'] ?? ''), 'twitter_title' => (string)($page['twitter_title'] ?? ''), 'twitter_description' => (string)($page['twitter_description'] ?? ''), 'no_index' => (int)($page['no_index'] ?? 0), 'no_follow' => (int)($page['no_follow'] ?? 0), 'hero_overlay_color' => $overlay, 'hero_overlay_opacity' => $overlay !== '' ? 0.4 : 0, 'text_theme' => in_array($textColor, ['#fff', '#ffffff', 'white'], true) ? 'light' : 'dark', 'markets' => implode(',', array_filter(array_map( fn(string $t): int => $this->marketUidByTitle[$this->normTitle($t)] ?? 0, $industries ))), ]; // FAL: card image, logo, hero, og/twitter if ($card !== null) { $story['card_image'] = $attach( array_slice($this->refs('tt_content', (int)$card['uid'], 'tx_mask_web__markets_card_frontimage'), 0, 1), self::TABLE, $storyKey, 'card_image' ); $story['customer_logo'] = $attach( array_slice($this->refs('tt_content', (int)$card['uid'], 'tx_mask_web__markets_card_customerlogo'), 0, 1), self::TABLE, $storyKey, 'customer_logo' ); } $heroRefs = $promo !== null ? $this->refs('tt_content', (int)$promo['uid'], 'tx_mask_web__success_story__full_width_promo_image') : []; if ($heroRefs === []) { $heroRefs = $this->refs('pages', $pageUid, 'heroimage_big'); } $story['hero_bgimage'] = $attach(array_slice($heroRefs, 0, 1), self::TABLE, $storyKey, 'hero_bgimage'); $story['og_image'] = $attach(array_slice($this->refs('pages', $pageUid, 'og_image'), 0, 1), self::TABLE, $storyKey, 'og_image'); $story['twitter_image'] = $attach(array_slice($this->refs('pages', $pageUid, 'twitter_image'), 0, 1), self::TABLE, $storyKey, 'twitter_image'); // content elements $ceKeys = []; $ceCounter = 0; $itemCounter = 0; foreach ($ceSpecs as $spec) { $ceKey = 'NEWCE' . (++$ceCounter); $fields = $spec['fields']; $fields['pid'] = $storagePid; $fields['CType'] = $spec['CType']; $fields['colPos'] = 999; foreach ($spec['files'] ?? [] as $f) { $fields[$f['field']] = $attach($f['refs'], 'tt_content', $ceKey, $f['field']); } if (($spec['CType'] === 'vitec_columns') && $this->collectionTable !== '' && !empty($spec['items'])) { $itemKeys = []; foreach ($spec['items'] as $item) { $itemKey = 'NEWITEM' . (++$itemCounter); $itemFields = $item['fields']; $itemFields['pid'] = $storagePid; foreach ($item['files'] ?? [] as $f) { $itemFields[$f['field']] = $attach($f['refs'], $this->collectionTable, $itemKey, $f['field']); } $datamap[$this->collectionTable][$itemKey] = $itemFields; $itemKeys[] = $itemKey; } $fields[$this->cbCol('tt_content', 'items')] = implode(',', $itemKeys); } $datamap['tt_content'][$ceKey] = $fields; $ceKeys[] = $ceKey; } $story['content_elements'] = implode(',', $ceKeys); $datamap[self::TABLE][$storyKey] = $story; $dh = GeneralUtility::makeInstance(DataHandler::class); $dh->start($datamap, []); $dh->process_datamap(); if ($dh->errorLog !== []) { throw new \RuntimeException('DataHandler: ' . implode(' | ', array_slice($dh->errorLog, 0, 3))); } return [$slugTail, $existing ? 'reimported' : 'created', count($ceSpecs), $imgCount, implode(',', $industries)]; } // ========================================================== content transform /** @return array> ordered CE specs */ private function transformContent(int $pageUid): array { $specs = []; $roots = $this->rootsByPid[$pageUid] ?? []; // intro (colPos 5): hero subline — first element, CTA buttons stripped foreach ($roots as $ce) { if ((int)$ce['colPos'] === 5 && $ce['CType'] === 'text' && !empty($ce['bodytext'])) { $body = preg_replace('/]*>(?:(?!<\/p>).)*btn(?:(?!<\/p>).)*<\/p>/is', '', (string)$ce['bodytext']) ?? ''; if (trim(strip_tags($body)) !== '') { $specs[] = ['CType' => 'text', 'fields' => ['header' => '', 'bodytext' => trim($body)], 'files' => []]; } } } foreach ($roots as $ce) { if ((int)$ce['colPos'] === 0) { $this->transformCe($ce, $specs); } } return $specs; } private function transformCe(array $ce, array &$specs): void { $ctype = (string)$ce['CType']; if (in_array($ctype, self::SKIP_CTYPES, true)) { return; } if (in_array($ctype, self::CONTAINER_CTYPES, true)) { $this->transformContainer($ce, $specs); return; } switch ($ctype) { case 'text': $specs[] = $this->textOrQuoteSpec($ce); break; case 'image': case 'textmedia': $field = $ctype === 'image' ? 'image' : 'assets'; $spec = [ 'CType' => $ctype, 'fields' => array_filter([ 'header' => (string)($ce['header'] ?? ''), 'header_layout' => (int)($ce['header_layout'] ?? 0), 'bodytext' => (string)($ce['bodytext'] ?? ''), ], static fn($v) => $v !== '' && $v !== 0), 'files' => [], ]; $refs = $this->refs('tt_content', (int)$ce['uid'], $field); if ($refs !== []) { $spec['files'][] = ['field' => $field, 'refs' => $refs]; } if ($spec['files'] !== [] || ($spec['fields']['bodytext'] ?? '') !== '') { $specs[] = $spec; } break; case 'html': if (!empty($ce['bodytext'])) { $specs[] = ['CType' => 'html', 'fields' => ['bodytext' => (string)$ce['bodytext']], 'files' => []]; } break; default: // unknown → keep text-ish content if any if (!empty($ce['bodytext']) || !empty($ce['header'])) { $specs[] = ['CType' => 'text', 'fields' => [ 'header' => (string)($ce['header'] ?? ''), 'bodytext' => (string)($ce['bodytext'] ?? ''), ], 'files' => []]; } } } /** Two clean columns -> vitec_columns; everything else flattens in order. */ private function transformContainer(array $ce, array &$specs): void { $children = $this->childrenByParent[(int)$ce['uid']] ?? []; if ($children === []) { return; } $byCol = []; foreach ($children as $child) { $byCol[(int)$child['colPos'] % 100][] = $child; } ksort($byCol); $isSimple = static fn(array $c): bool => in_array($c['CType'], ['text', 'image'], true) && stripos((string)($c['bodytext'] ?? ''), ' $ok && $isSimple($c), true); if ($allSimple) { $items = []; foreach ($byCol as $colIdx => $colCes) { $side = $colIdx === $cols[0] ? 'left' : 'right'; foreach ($colCes as $c) { $t = $this->collectionTable; if ($c['CType'] === 'text') { $items[] = ['fields' => [ $this->cbCol($t, 'column') => $side, $this->cbCol($t, 'item_type') => 'text', $this->cbCol($t, 'headline') => (string)($c['header'] ?? ''), $this->cbCol($t, 'text') => (string)($c['bodytext'] ?? ''), ], 'files' => []]; } else { $refs = $this->refs('tt_content', (int)$c['uid'], 'image'); $items[] = ['fields' => [ $this->cbCol($t, 'column') => $side, $this->cbCol($t, 'item_type') => 'image', $this->cbCol($t, 'headline') => (string)($c['header'] ?? ''), ], 'files' => $refs !== [] ? [['field' => $this->cbCol($t, 'image'), 'refs' => array_slice($refs, 0, 1)]] : []]; } } } $specs[] = [ 'CType' => 'vitec_columns', 'fields' => ['header' => (string)($ce['header'] ?? ''), $this->cbCol('tt_content', 'layout') => 'cols_50_50'], 'files' => [], 'items' => $items, ]; return; } // flatten: children first; the container header only becomes a heading // when the container actually contributed content (skips e.g. the old // "Related Success Stories" section whose children are all shortcuts). $childSpecs = []; foreach ($byCol as $colCes) { foreach ($colCes as $child) { $this->transformCe($child, $childSpecs); } } $header = trim((string)($ce['header'] ?? '')); if ($childSpecs !== [] && $header !== '' && !str_starts_with($header, '///')) { $specs[] = ['CType' => 'text', 'fields' => ['header' => $header], 'files' => []]; } foreach ($childSpecs as $cs) { $specs[] = $cs; } } /** blockquote text CEs become vitec_quotation; the rest stay text. */ private function textOrQuoteSpec(array $ce): array { $body = (string)($ce['bodytext'] ?? ''); $header = (string)($ce['header'] ?? ''); if (stripos($body, ']*>(.*)<\/blockquote>/is', $body, $m)) { $inner = $m[1]; // attribution paragraph:

— Name
Position

if (preg_match('/

\s*\s*(?:—|—|-)?\s*(.*?)<\/strong>\s*(?:)?\s*(.*?)<\/p>\s*$/is', $inner, $a)) { $name = trim(strip_tags($a[1])); $position = trim(strip_tags($a[2])); $inner = (string)preg_replace('/

\s*\s*(?:—|—|-)?.*?<\/p>\s*$/is', '', $inner); } $quote = trim(strip_tags($inner)); $quote = trim($quote, "“”\"' \u{201C}\u{201D}"); } return ['CType' => 'vitec_quotation', 'fields' => [ $this->cbCol('tt_content', 'quote') => $quote, $this->cbCol('tt_content', 'quote_name') => $name, $this->cbCol('tt_content', 'quote_position') => $position, ], 'files' => []]; } return ['CType' => 'text', 'fields' => array_filter([ 'header' => strcasecmp($header, 'Quote') === 0 ? '' : $header, 'header_layout' => (int)($ce['header_layout'] ?? 0), 'bodytext' => $body, ], static fn($v) => $v !== '' && $v !== 0), 'files' => []]; } // ==================================================================== files /** Old-site identifier -> new-site sys_file uid (staged under FILES_BASE). */ private function fileUidFor(string $identifier): ?int { if (isset($this->fileUidCache[$identifier])) { return $this->fileUidCache[$identifier] ?: null; } try { $file = GeneralUtility::makeInstance(ResourceFactory::class) ->getFileObjectFromCombinedIdentifier('1:' . self::FILES_BASE . $identifier); $uid = $file !== null ? (int)$file->getUid() : 0; } catch (\Throwable $e) { $uid = 0; } $this->fileUidCache[$identifier] = $uid; return $uid ?: null; } }