Success-story migration + new plugins + JSON/UX polish

- Success Stories: full migration from old site (48/48, audited),
  markets n:n, quotation content block, columns content block,
  pretty SEO detail URLs via SuccessStoryPathRewrite middleware,
  list/detail split (list page 4 / story page), detailUrl/backUrl
- New plugins: VITEC Locations (grid/list/map + RTE map text),
  VITEC Customer Logos (color/bw logic, only-show-selected),
  VITEC Card (one plugin for product/story/market/solution
  with reloading FlexForm + custom backend preview renderer)
- Eventlist: layout dropdown (list/grid/teaserbar) in settings
- Hero section CB: Images/Video tabs, background video + overlay
- Product JSON: full category rootline (parents), fixed missing
  ConnectionPool import (all-products crash), category tree map
- Backend preview CSS: container-query responsive (narrow columns)
- Docs: ISO architecture spec, root + extension READMEs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
khaccount
2026-07-24 09:53:33 +02:00
parent aafcb5dcb6
commit eae40f4286
110 changed files with 8502 additions and 279 deletions

View File

@@ -11,6 +11,7 @@ use Evomedien\Vitec\Domain\Repository\ProductRepository;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference;
use TYPO3\CMS\Core\Resource\ResourceFactory;
@@ -406,15 +407,61 @@ class ProductListJsonRenderer
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return array_map(function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
'parents' => $this->getCategoryParents((int)$cat['uid']),
];
}, $categories);
}
/** @var array<int,array{parent:int,title:string}>|null Lazy uid => [parent,title] map of all categories. */
private ?array $categoryTreeMap = null;
/**
* All ancestor categories of one category, ROOT FIRST (excluding itself).
* Backed by a once-per-request map of sys_category — cheap for any number
* of products. Cycle-safe.
*
* @return array<int,array{uid:int,title:string}>
*/
private function getCategoryParents(int $categoryUid): array
{
if ($this->categoryTreeMap === null) {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$rows = $qb
->select('uid', 'parent', 'title')
->from('sys_category')
->where(
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
$this->categoryTreeMap = [];
foreach ($rows as $row) {
$this->categoryTreeMap[(int)$row['uid']] = [
'parent' => (int)$row['parent'],
'title' => (string)$row['title'],
];
}
}
$parents = [];
$seen = [$categoryUid => true];
$current = $this->categoryTreeMap[$categoryUid]['parent'] ?? 0;
while ($current > 0 && isset($this->categoryTreeMap[$current]) && !isset($seen[$current])) {
$seen[$current] = true;
array_unshift($parents, ['uid' => $current, 'title' => $this->categoryTreeMap[$current]['title']]);
$current = $this->categoryTreeMap[$current]['parent'];
}
return $parents;
}
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)