Megamenu page-tree panels; generic slug normaliser
- evo_megamenu_json: new entry layout "pagetree" - the panel columns mirror the live subtree of the page the entry links to (one child per column, grandchildren as items, getMenu() restrictions apply), so the menu can never age against the page structure again. The stale 9-market panel after the markets realign was the trigger. Curated columns are ignored on this layout; teasers and the featured slot stay editorial. Same columns shape in the payload - front ends need no change beyond knowing the new layout string - new vitec:normalize-slugs --root=<slug> [--style=plain|and]: recursively realigns every slug below a root to <parent-slug>/<slugified-title>, cascading, with a sibling-collision warning; EXT:redirects covers the old URLs. TYPO3's slug generator keeps slashes from page titles (third hit: rtcc-/-rtic), this sweeps a whole tree. Run on /solutions: four fixes, including the long-standing passenger page and solution page 467. The misspelled "engagament" slug stays deliberately - it follows the page title and is fixed in the coordinated typo task Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
303defe24d
commit
cbe46cdba2
4 files changed
+182
-1
No files matched your search
@@ -86,7 +86,7 @@ final class MenuBuilder
|
||||
private function buildEntry(array $entry, int $stage, ContentObjectRenderer $cObj): array
|
||||
{
|
||||
$layout = (string)($entry['layout'] ?? 'columns');
|
||||
if (!in_array($layout, ['columns', 'teasers', 'catalog'], true)) {
|
||||
if (!in_array($layout, ['columns', 'teasers', 'catalog', 'pagetree'], true)) {
|
||||
$layout = 'columns';
|
||||
}
|
||||
|
||||
@@ -103,6 +103,16 @@ final class MenuBuilder
|
||||
];
|
||||
}
|
||||
|
||||
// Page-tree panels (2026-10-23): the columns mirror the live subtree
|
||||
// of the page the entry links to - one column per child, its children
|
||||
// as the items - so the menu can never age against the page structure
|
||||
// again (the stale 9-market panel after the markets realign was the
|
||||
// trigger). Curated columns are ignored on this layout; teasers and
|
||||
// the featured slot stay editorial.
|
||||
if ($layout === 'pagetree') {
|
||||
$columns = $this->treeColumns((string)$entry['link'], $cObj);
|
||||
}
|
||||
|
||||
$built = [
|
||||
'title' => (string)$entry['title'],
|
||||
'link' => $this->url((string)$entry['link'], $cObj),
|
||||
@@ -177,6 +187,42 @@ final class MenuBuilder
|
||||
return $built;
|
||||
}
|
||||
|
||||
/**
|
||||
* Columns from the page tree below the entry's linked page. getMenu()
|
||||
* applies the frontend restrictions (hidden, time, nav_hide, fe_group),
|
||||
* so an editor hides a page and it leaves the menu with the next flush.
|
||||
*
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private function treeColumns(string $entryLink, ContentObjectRenderer $cObj): array
|
||||
{
|
||||
$rootUid = 0;
|
||||
if (preg_match('/t3:\/\/page\?uid=(\d+)/', $entryLink, $matches)) {
|
||||
$rootUid = (int)$matches[1];
|
||||
} elseif (ctype_digit(trim($entryLink))) {
|
||||
$rootUid = (int)trim($entryLink);
|
||||
}
|
||||
if ($rootUid <= 0) {
|
||||
return [];
|
||||
}
|
||||
$columns = [];
|
||||
foreach ($this->pageRepository->getMenu($rootUid) as $child) {
|
||||
$items = [];
|
||||
foreach ($this->pageRepository->getMenu((int)$child['uid']) as $grandchild) {
|
||||
$items[] = [
|
||||
'title' => (string)(($grandchild['nav_title'] ?? '') !== '' ? $grandchild['nav_title'] : $grandchild['title']),
|
||||
'link' => $this->url('t3://page?uid=' . (int)$grandchild['uid'], $cObj),
|
||||
];
|
||||
}
|
||||
$columns[] = [
|
||||
'title' => (string)(($child['nav_title'] ?? '') !== '' ? $child['nav_title'] : $child['title']),
|
||||
'link' => $this->url('t3://page?uid=' . (int)$child['uid'], $cObj),
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------ internals
|
||||
|
||||
/** @return array<string,mixed>|null */
|
||||
|
||||
@@ -105,6 +105,7 @@ return [
|
||||
['label' => $ll . 'layout.columns', 'value' => 'columns'],
|
||||
['label' => $ll . 'layout.teasers', 'value' => 'teasers'],
|
||||
['label' => $ll . 'layout.catalog', 'value' => 'catalog'],
|
||||
['label' => $ll . 'layout.pagetree', 'value' => 'pagetree'],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.columns"><source>Columns</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.teasers"><source>Link column plus teaser cards</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.catalog"><source>Catalog (data-driven: categories and items from the site package, teaser cards as featured tiles)</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.layout.pagetree"><source>Page tree (data-driven: one column per child of the linked page, its children as items; curated columns are ignored)</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.menu_columns"><source>Columns</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.menu_columns.description"><source>One column per group. In the teaser layout only the first column is rendered, next to the cards.</source></trans-unit>
|
||||
<trans-unit id="tx_evomegamenujson_entry.menu_teasers"><source>Teaser cards</source></trans-unit>
|
||||
|
||||
Reference in new issue
Block a user