Sub-solutions filled from Joe's 39 workbooks; dummy assets wired

- Joe's 39 "- Completed" workbooks applied to all 39 sub-solution pages
  (#467-#515); both workbook inboxes are now gitignored and the tracked
  test workbooks untracked - filled deliveries live in the Nextcloud
- new Import\ProductFamilyAliases: the workbooks reference product
  FAMILIES in the product slots; 11 labels map to a representative
  product, consulted by BOTH importers after the exact title match and
  reported as "-> family alias"
- flip-card icons: dummy SVGs fileadmin/icons/flow/<name>.svg for Joe's
  10-name vocabulary, attached by the new attachIconFile() through the
  shared idempotent attachFalFile() (extracted from attachUrlFile);
  real icons later REPLACE those files in the Filelist (same sys_file
  uid), every card follows without a re-import
- image slots: numbered grey placeholders per page and slot
  (fileadmin/placeholders/pages/<pid>-NN.png, the id rendered into the
  image; generic grey as fallback). An existing reference is never
  overwritten; a real URL on re-import swaps the placeholder away
- page properties per sub-solution: og/twitter title + description
  mirror the SEO fields, canonical_link points at the page itself,
  abstract derived from the intro copy (~300 chars, word boundary),
  keywords from the curated PAGE_KEYWORDS set
- spec head-matter fix rides along (version table was stuck at 1.21,
  real state 1.23)

Open afterwards: 3 missing Datapath stories + re-apply, quotes on 16
story records (list to Joe), slug slash on page 467.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
orascheandClaude Fable 5 committed 2026-10-19 18:51:21 +02:00
1 parent 74502e60a3
commit d115ffb144
7 files changed
+401 -86

No files matched your search

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Import;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Joe's solution/market workbooks fill the product slots with FAMILY labels
* ("VSN Platforms", "End Points", "OG Platform"), not record titles. One
* card renders one product record, so every family maps to its
* representative product here - an editorial mapping (Olli, 2026-10-19),
* deliberately versioned next to the importers that read the workbooks
* instead of hidden in database state. Both workbook importers consult it
* AFTER their exact title match, so a real record title always wins.
*
* Keys are normalised like the importers' norm(): lowercase, '+' -> plus,
* everything non-alphanumeric stripped. Values are product slugs - stable
* across environments, unlike uids.
*/
final class ProductFamilyAliases
{
private const MAP = [
'vsnplatforms' => 'vsn-v3',
'visionsc' => 'visionsc-uhd2',
'visionio' => 'visionio-sd14',
'wallcontrol' => 'aetria-wallcontrol',
'imagegraphics' => 'image-4k',
'xseries' => 'fx4',
'endpoints' => 'avedia-end-points',
'mgwproductfamily' => 'mgw-ace-encoder',
'diamondip' => 'diamond-ip-og-ip-to-ip-encoder',
'ogplatform' => 'mgw-diamond-og-sdi-composite-blade-encoder',
'capturecards' => 'visionav-hd',
];
/**
* @return array<string,mixed>|null the aliased product row, or null when
* the label is no known family
*/
public static function resolve(string $normalisedLabel): ?array
{
$slug = self::MAP[$normalisedLabel] ?? null;
if ($slug === null) {
return null;
}
$row = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('tx_vitec_domain_model_product')
->fetchAssociative(
'SELECT * FROM tx_vitec_domain_model_product WHERE deleted = 0 AND slug = ?',
[$slug]
);
return $row ?: null;
}
}