Align product records with sitemap V2; product family model; CLI workbook import

- vitec:migrate-product-structure: exact V2 titles, Milestone typo fix (incl.
  slug), QTX100 stray VSN category removed, Aligo record moved to its own
  category with slug /product/aligo, new Aligo Workstation record takes over
  /product/aligo-workstation
- product families (sitemap column K) are ordinary content pages now:
  vitec:remove-family-records deletes the interim landing records again,
  reset_subproduct_flags.php clears the misused subproduct flag (the list
  renderer has always excluded subproduct=1)
- vitec:import-product-workbook: non-interactive counterpart of the module
  import (same reader, mapping and DataHandler path); Arqa imported, Aligo
  re-applied with the corrected capabilities markup
- ProductXlsxReader: new :copy cell selector (Body Copy with CTA fallback)
  absorbs the agency's column drift; default teaser mapping uses it
- ProductListJsonRenderer: selected categories now match their whole subtree,
  results ordered by category tree position, sheet order within a category
- read-only diagnostics: check_product_structure.php, check_productlist_ces.php
This commit is contained in:
orasche committed 2026-09-04 12:59:17 +02:00
1 parent 7833e1b668
commit 5499ca54af
12 files changed
+1186 -121

No files matched your search

@@ -170,6 +170,22 @@ final class ProductXlsxReader
);
}
// Drift-proof copy selector: the agency fills text sometimes into
// "Body Copy" (D), sometimes into "CTA / Card Copy" (E) - Aligo's
// hero one-liner sits in D, Arqa's in E. `copy` reads body and
// falls back to cta, so one stored mapping fits every delivery.
$copy = trim((string)($component['body'] ?? ''));
if ($copy === '') {
$copy = trim((string)($component['cta'] ?? ''));
}
if ($copy !== '') {
$groups['Components'][] = $this->entry(
'c:' . $name . '#' . $component['occurrence'] . ':copy',
$name . $suffix . ' · Copy (Body, sonst CTA)',
$copy
);
}
// Section-intro rows ("... — H2") additionally as ONE ready RTE
// value: heading plus paragraph in the exact markup the finished
// MGW-Diamond product stores in `textrelatedproducts` - imported
@@ -250,10 +266,18 @@ final class ProductXlsxReader
$value = $parsed['seo'][substr($selector, 2)] ?? null;
return is_string($value) && $value !== '' ? $value : null;
}
if (str_starts_with($selector, 'c:') && preg_match('/^c:(.+)#(\d+):(title|body|cta)$/', $selector, $m)) {
if (str_starts_with($selector, 'c:') && preg_match('/^c:(.+)#(\d+):(title|body|cta|copy)$/', $selector, $m)) {
foreach ($parsed['components'] ?? [] as $component) {
if ((string)$component['component'] === $m[1] && (int)$component['occurrence'] === (int)$m[2]) {
$value = (string)($component[$m[3]] ?? '');
if ($m[3] === 'copy') {
$value = trim((string)($component['body'] ?? ''));
if ($value === '') {
$value = trim((string)($component['cta'] ?? ''));
}
$value = trim((string)preg_replace('/\|\s*CTA:.*$/su', '', $value));
} else {
$value = (string)($component[$m[3]] ?? '');
}
return $value !== '' ? $value : null;
}
}