Product texts: mapping completed; product model: solutions + resources
Two work packages, one deploy.
Workbook field mapping completed (2026-09-18)
- highlights <- g:Technical Highlight (new spec-row component of the
full agency delivery; composeGroup() is generic, no change needed)
- applications <- g:Why Choose Card (benefit cards as one block)
- seometa <- new composed source x:seometa (Introduction body trimmed
at a word boundary to ~155 chars - the SEO sheet ships no meta
description, a trimmed intro beats an empty tag)
- teaser stays the hero one-liner, settling the varchar(255) question
- fix: "Save mapping" now merges over the stored mapping instead of
overwriting it - an empty dropdown no longer wipes a stored selector
Product model: solutions relation and Resources tab (2026-09-24)
- closes the gaps the full workbook walkthrough exposed (rows the
import deliberately skips because the model had no place for them)
- solutions: MM multi select to tx_vitec_domain_model_solution
(tx_vitec_product_solution_mm, relatedprodukt pattern), emitted by
both product renderers as solution cards in the shared shape
- Resources tab: inline child table tx_vitec_product_resource - per
entry a type (documentation/support/datasheet), editable card title
and text, an icon (free text with a value-picker seed set) and one
link (page/url/file/record via the link wizard, so the Download tab
works too). Deliberately no Extbase model: only the UserFunc path
reads the rows.
- JSON `resources` (detail payload only, like heroimage): list envelope
{layout, items[]}; a card whose link is empty or unresolvable is not
emitted; layout switches to carousel above three cards
- spec raised to v1.18 (7.15 member table, revision row; the footer had
been stuck at v1.15 since 1.16/1.17)
Requires on the server:
vendor/bin/typo3 database:updateschema "*.add,*.change"
vendor/bin/typo3 cache:flush
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4a7ea387a0
commit
3df66b1353
9 files changed
+394
-4
No files matched your search
@@ -227,6 +227,10 @@ final class ProductXlsxReader
|
||||
if ($seoTitle !== null) {
|
||||
$groups['Composed'][] = $this->entry('x:seotitle', 'Hero title — Intro heading (SEO title)', $seoTitle);
|
||||
}
|
||||
$seoMeta = $this->composeSeoMeta($parsed);
|
||||
if ($seoMeta !== null) {
|
||||
$groups['Composed'][] = $this->entry('x:seometa', 'Introduction body, trimmed to ~155 chars (meta description)', $seoMeta);
|
||||
}
|
||||
|
||||
foreach ($parsed['seo'] ?? [] as $header => $value) {
|
||||
$groups['Page SEO Check'][] = $this->entry('s:' . $header, 'SEO · ' . $header, (string)$value);
|
||||
@@ -262,6 +266,9 @@ final class ProductXlsxReader
|
||||
if ($selector === 'x:seotitle') {
|
||||
return $this->composeSeoTitle($parsed);
|
||||
}
|
||||
if ($selector === 'x:seometa') {
|
||||
return $this->composeSeoMeta($parsed);
|
||||
}
|
||||
if (str_starts_with($selector, 's:')) {
|
||||
$value = $parsed['seo'][substr($selector, 2)] ?? null;
|
||||
return is_string($value) && $value !== '' ? $value : null;
|
||||
@@ -310,6 +317,37 @@ final class ProductXlsxReader
|
||||
return $hero . ' — ' . $intro;
|
||||
}
|
||||
|
||||
/**
|
||||
* Meta description from the Product Introduction body: plain text,
|
||||
* whitespace collapsed, cut at a word boundary to the customary ~155
|
||||
* characters with an ellipsis. Decision 2026-09-18: composed here because
|
||||
* the SEO sheet still ships no meta description at all - a trimmed intro
|
||||
* beats an empty tag at go-live, and the editor can always overwrite.
|
||||
*/
|
||||
private function composeSeoMeta(array $parsed): ?string
|
||||
{
|
||||
foreach ($parsed['components'] ?? [] as $component) {
|
||||
if ((string)($component['component'] ?? '') === 'Product Introduction'
|
||||
&& (int)($component['occurrence'] ?? 1) === 1
|
||||
) {
|
||||
$text = trim((string)preg_replace('/\s+/u', ' ', strip_tags((string)($component['body'] ?? ''))));
|
||||
if ($text === '') {
|
||||
return null;
|
||||
}
|
||||
if (mb_strlen($text) <= 155) {
|
||||
return $text;
|
||||
}
|
||||
$cut = mb_substr($text, 0, 152);
|
||||
$lastSpace = mb_strrpos($cut, ' ');
|
||||
if ($lastSpace !== false && $lastSpace > 100) {
|
||||
$cut = mb_substr($cut, 0, $lastSpace);
|
||||
}
|
||||
return rtrim($cut, " ,;:") . ' …';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* One component row as a heading/paragraph pair. The markup - centred h3
|
||||
* with an inner span, centred p - is copied verbatim from what the
|
||||
|
||||
Reference in new issue
Block a user