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:
khaccountandClaude Fable 5 committed 2026-07-24 09:53:33 +02:00
1 parent aafcb5dcb6
commit eae40f4286
110 files changed
+8502 -279

No files matched your search

@@ -72,6 +72,22 @@ class UsecaseListJsonRenderer
public function renderForRecord(array $contentElement): string
{
try {
// Detail context: when the request carries a story argument the
// list stays silent — the usecaseshow plugin on the same page
// renders the story. Keeps old SEO URLs on one page.
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
$routing = $request?->getAttribute('routing');
$detailParam = null;
if ($routing instanceof \TYPO3\CMS\Core\Routing\PageArguments) {
$detailParam = $routing->getRouteArguments()['tx_vitec_usecaseshow']['usecase'] ?? null;
}
if ($detailParam === null || $detailParam === '') {
$detailParam = ($request?->getQueryParams() ?? [])['tx_vitec_usecaseshow']['usecase'] ?? null;
}
if ($detailParam !== null && $detailParam !== '') {
return '';
}
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
@@ -92,8 +108,28 @@ class UsecaseListJsonRenderer
->fetchAllAssociative();
$serializer = GeneralUtility::makeInstance(UsecaseSerializer::class);
// Detail links: the PUBLIC URL is the PARENT path of the
// FlexForm-selected "Single PID" page plus the story slug —
// /success-stories/<slug>, not /success-stories/story/<slug>.
// The SuccessStoryPathRewrite middleware maps it back to the
// detail subpage at request time.
$singlePid = (int)($settings['singlePid'] ?? 0);
$detailBase = '';
if ($singlePid > 0) {
$detailPath = rtrim($this->resolvePageUrl($singlePid), '/');
$parent = str_contains($detailPath, '/') ? substr($detailPath, 0, (int)strrpos($detailPath, '/')) : '';
$detailBase = $parent !== '' ? $parent : $detailPath;
}
$usecases = array_map(
static fn (array $u): array => $serializer->serializeListItem($u),
static function (array $u) use ($serializer, $detailBase): array {
$item = $serializer->serializeListItem($u);
$item['detailUrl'] = ($detailBase !== '' && ($item['slug'] ?? '') !== '')
? $detailBase . '/' . ltrim((string)$item['slug'], '/')
: null;
return $item;
},
$rows
);
@@ -109,4 +145,15 @@ class UsecaseListJsonRenderer
return '';
}
}
/** Resolve a page uid to its frontend path (route enhancer aware). */
private function resolvePageUrl(int $pageUid): string
{
try {
$cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
return (string)$cObj->typoLink_URL(['parameter' => (string)$pageUid]);
} catch (\Throwable $e) {
return '';
}
}
}