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), an editable card title/text,
an icon (free text with a value-picker seed set) and one link
(page/url/file/record via the link wizard). Deliberately no Extbase
model: only the UserFunc path reads the rows.
- datasheets: own MM selection of download records below the resource
list (tx_vitec_product_datasheet_mm); emitted into the SAME
resources envelope as ready-made type=datasheet cards (title/teaser/
icon from the record, url = the public /download/<slug> route, uid
fallback - private/hidden downloads 404 as everywhere)
- JSON `resources` (detail payload only, like heroimage): list envelope
{layout, items[]}; a hand-written card whose link is empty or
unresolvable is not emitted; layout switches to carousel above three
cards in total
- solutions hybrid (same day, after analysing all 100 workbooks: their
Solution Cards reference the 8 GROUPS in 98% of cases): new is_group
flag on the solution model, new idempotent CLI
vitec:create-solution-groups (8 group records; title source is
AlignSolutionsCommand::TARGET - now public -, category = the group's
own category, detail_page = the /solutions group page with -N
disambiguation). Group records are invisible to the solution list
and to vitec:align-solutions and only selectable in the product
relation; product solution cards carry isGroup
- spec raised to v1.18 (7.15 member table, 7.13.3 note, 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>
226 lines
4.3 KiB
PHP
226 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace Evomedien\Vitec\Domain\Model;
|
|
|
|
use TYPO3\CMS\Extbase\Domain\Model\Category;
|
|
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
|
|
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
|
|
|
|
class Solution extends AbstractEntity
|
|
{
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $title = '';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $slug = '';
|
|
|
|
/**
|
|
* Page uid presenting this solution (TCA type "group", allowed: pages).
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $detailPage = 0;
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $subtitle = '';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $teaser = '';
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $description = '';
|
|
|
|
/**
|
|
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference
|
|
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
|
|
*/
|
|
protected $image = null;
|
|
|
|
/**
|
|
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category>
|
|
* @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
|
|
*/
|
|
protected $categories;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->categories = new ObjectStorage();
|
|
}
|
|
|
|
/* ---------------------------------------------- */
|
|
/* Getter und Setter */
|
|
/* ---------------------------------------------- */
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getTitle(): string
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
/**
|
|
* @param string $title
|
|
*/
|
|
public function setTitle(string $title): void
|
|
{
|
|
$this->title = $title;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getSlug(): string
|
|
{
|
|
return $this->slug;
|
|
}
|
|
|
|
/**
|
|
* @param string $slug
|
|
*/
|
|
public function setSlug(string $slug): void
|
|
{
|
|
$this->slug = $slug;
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getDetailPage(): int
|
|
{
|
|
return $this->detailPage;
|
|
}
|
|
|
|
/**
|
|
* @param int $detailPage
|
|
*/
|
|
public function setDetailPage(int $detailPage): void
|
|
{
|
|
$this->detailPage = $detailPage;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getSubtitle(): string
|
|
{
|
|
return $this->subtitle;
|
|
}
|
|
|
|
/**
|
|
* @param string $subtitle
|
|
*/
|
|
public function setSubtitle(string $subtitle): void
|
|
{
|
|
$this->subtitle = $subtitle;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getTeaser(): string
|
|
{
|
|
return $this->teaser;
|
|
}
|
|
|
|
/**
|
|
* @param string $teaser
|
|
*/
|
|
public function setTeaser(string $teaser): void
|
|
{
|
|
$this->teaser = $teaser;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getDescription(): string
|
|
{
|
|
return $this->description;
|
|
}
|
|
|
|
/**
|
|
* @param string $description
|
|
*/
|
|
public function setDescription(string $description): void
|
|
{
|
|
$this->description = $description;
|
|
}
|
|
|
|
/**
|
|
* @return \TYPO3\CMS\Extbase\Domain\Model\FileReference|null
|
|
*/
|
|
public function getImage()
|
|
{
|
|
return $this->image;
|
|
}
|
|
|
|
/**
|
|
* @param \TYPO3\CMS\Extbase\Domain\Model\FileReference $image
|
|
*/
|
|
public function setImage(\TYPO3\CMS\Extbase\Domain\Model\FileReference $image): void
|
|
{
|
|
$this->image = $image;
|
|
}
|
|
|
|
/**
|
|
* @param Category $category
|
|
*/
|
|
public function addCategory(Category $category): void
|
|
{
|
|
$this->categories->attach($category);
|
|
}
|
|
|
|
/**
|
|
* @param Category $categoryToRemove
|
|
*/
|
|
public function removeCategory(Category $categoryToRemove): void
|
|
{
|
|
$this->categories->detach($categoryToRemove);
|
|
}
|
|
|
|
/**
|
|
* @return ObjectStorage<Category>
|
|
*/
|
|
public function getCategories(): ObjectStorage
|
|
{
|
|
return $this->categories;
|
|
}
|
|
|
|
/**
|
|
* @param ObjectStorage<Category> $categories
|
|
*/
|
|
public function setCategories(ObjectStorage $categories): void
|
|
{
|
|
$this->categories = $categories;
|
|
}
|
|
|
|
/**
|
|
* Group-level record (one of the 8 solution groups) - hybrid decision
|
|
* 2026-09-24. Excluded from lists; selectable in product relations.
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $isGroup = false;
|
|
|
|
public function getIsGroup(): bool
|
|
{
|
|
return $this->isGroup;
|
|
}
|
|
|
|
public function setIsGroup(bool $isGroup): void
|
|
{
|
|
$this->isGroup = $isGroup;
|
|
}
|
|
}
|