Product list: success stories on the all-products element

The big product list on /products (all products, no category context)
deliberately delivered the empty successStories envelope; the front end
now renders story cards there too and filters them client-side, so the
element aggregates over the WHOLE product category tree instead:

- new allProductCategoryUids(): root category 'Product' (same detection
  as ProductFinderJsonRenderer), children plus descendants depth-first,
  fed through the existing successStoriesFor()
- per-category elements are untouched; forCategories keeps carrying the
  category uids a story is attached to, so the ?cat=/?subcat= filter
  behaves identically on both element kinds
- layout rule unchanged: first contributing category in tree order

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
orascheandClaude Fable 5 committed 2026-09-16 14:43:00 +02:00
1 parent 55f7f82a42
commit 4a7ea387a0
1 file changed
+34 -2
@@ -207,9 +207,13 @@ class ProductListJsonRenderer
// Success stories the editor attached to these categories // Success stories the editor attached to these categories
// (sys_category, Options tab). They ride along with the list // (sys_category, Options tab). They ride along with the list
// so the front end can hide them with the same category // so the front end can hide them with the same category
// filter it applies to the products. // filter it applies to the products. An all-products element
// aggregates over the WHOLE product category tree (change
// 2026-09-16 on front-end request - it used to deliver the
// empty envelope), so forCategories keeps driving the
// ?cat=/?subcat= filter exactly like on per-category lists.
'successStories' => $this->successStoriesFor( 'successStories' => $this->successStoriesFor(
$allProducts ? [] : $categoryUids, ($allProducts || $categoryUids === []) ? $this->allProductCategoryUids() : $categoryUids,
(int)($settings['storysinglepid'] ?? 0) (int)($settings['storysinglepid'] ?? 0)
), ),
]; ];
@@ -375,6 +379,34 @@ class ProductListJsonRenderer
* @param int[] $categoryUids * @param int[] $categoryUids
* @return int[] * @return int[]
*/ */
/**
* The whole product category tree - the root's children plus all
* descendants in depth-first backend order. Story source for elements
* that render all products. Root detection mirrors
* ProductFinderJsonRenderer: the root-level sys_category named 'Product'.
*
* @return int[]
*/
private function allProductCategoryUids(): array
{
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$rootUid = (int)($queryBuilder->select('uid')->from('sys_category')
->where(
$queryBuilder->expr()->eq('parent', 0),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('title', $queryBuilder->createNamedParameter('Product', ParameterType::STRING))
)
->setMaxResults(1)
->executeQuery()->fetchOne() ?: 0);
if ($rootUid <= 0) {
return [];
}
// Drop the root itself - stories hang on the categories below it.
return array_values(array_diff($this->expandWithDescendants([$rootUid]), [$rootUid]));
}
private function expandWithDescendants(array $categoryUids): array private function expandWithDescendants(array $categoryUids): array
{ {
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)