Products: categories and sub-categories become clickable pages

The 2026-11-02 menu build created the 3 categories and 9 sub-categories
as sysfolders (labels); every level must be a clickable page.

- build-product-overviews: six sub-categories adopt the 2026-10 category
  pages (Joe's workbooks; content, slug and SEO kept, sitemap name as
  nav_title), the remaining folders become pages with generated content,
  Capture & Graphics gets the main column of Graphics & Multi-Display and
  Video Capture copied in. Pages move/convert by direct UPDATE (the
  redirects slug hook would rewrite the flat child slugs); three 2026-10
  pages leave the menus but stay reachable. Warns about a sys_redirect
  on a target slug.
- ProvideProductCatalog: categories[].link and groups[].link (additive),
  menu title = nav_title when set, only filled groups/categories count.
  The menu search reads both levels as pages too.
- build-why-vitec-bands: band at the top of the main column; on pages
  with an existing "Why VITEC?" section the context's icons go onto the
  cards whose title matches the icon theme (16 icons on 4 pages).
- No browser cache headers on the page JSON: headless enables
  sendCacheHeaders and TYPO3 14 caches pages for a year by default, so
  every /_json/ response carried max-age=~31536000.
- Spec v1.32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
orascheandClaude Opus 5.5 committed 2026-11-05 12:05:04 +01:00
1 parent 73e4298b75
commit bd7a145afa
6 files changed
+627 -79

No files matched your search

@@ -77,8 +77,10 @@ final class ProductMenuSearchJsonRenderer
}
/**
* The overview pages of the products menu tree (/products > folder >
* folder > page, see ProvideProductCatalog) with their menu path.
* The overview pages of the products menu tree (/products > category >
* sub-category > page, see ProvideProductCatalog) with their menu path.
* Category and sub-category are pages since 2026-11-05 (folders before -
* both are read).
*
* @return array<int,array{uid:int,title:string,link:string,path:string[]}> page uid => overview
*/
@@ -96,7 +98,7 @@ final class ProductMenuSearchJsonRenderer
return [];
}
$queryBuilder = $pool->getQueryBuilderForTable('pages');
return $queryBuilder->select('uid', 'pid', 'title', 'slug')->from('pages')
$rows = $queryBuilder->select('uid', 'pid', 'title', 'nav_title', 'slug')->from('pages')
->where(
$queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($parents, ArrayParameterType::INTEGER)),
$queryBuilder->expr()->in('doktype', $queryBuilder->createNamedParameter($doktypes, ArrayParameterType::INTEGER)),
@@ -104,9 +106,16 @@ final class ProductMenuSearchJsonRenderer
)
->orderBy('sorting')
->executeQuery()->fetchAllAssociative();
// menu label = nav_title when set (same rule as ProvideProductCatalog)
foreach ($rows as &$row) {
if (trim((string)($row['nav_title'] ?? '')) !== '') {
$row['title'] = $row['nav_title'];
}
}
return $rows;
};
$categories = array_column($level([$root], [254]), null, 'uid');
$groups = array_column($level(array_keys($categories), [254]), null, 'uid');
$categories = array_column($level([$root], [1, 254]), null, 'uid');
$groups = array_column($level(array_keys($categories), [1, 254]), null, 'uid');
$overviews = [];
foreach ($level(array_keys($groups), [1]) as $page) {
$group = $groups[(int)$page['pid']];