Skip to content

Commit 72f2075

Browse files
[5.x] CP nav reordering fixes (#11054)
Co-authored-by: Jason Varga <[email protected]>
1 parent 4681164 commit 72f2075

File tree

6 files changed

+826
-268
lines changed

6 files changed

+826
-268
lines changed

src/CP/Navigation/NavBuilder.php

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Statamic\CP\Navigation;
44

55
use Exception;
6+
use Illuminate\Support\Collection;
67
use Illuminate\Support\Facades\Cache;
78
use Statamic\Facades\Blink;
89
use Statamic\Facades\Preference;
@@ -254,8 +255,8 @@ protected function applyPreferenceOverrides($preferences = null)
254255
->each(fn ($overrides) => $this->createPendingItemsForSection($overrides))
255256
->each(fn ($overrides) => $this->applyPreferenceOverridesForSection($overrides));
256257

257-
if ($navPreferencesConfig['reorder']) {
258-
$this->setSectionOrder($sections);
258+
if ($reorder = $navPreferencesConfig['reorder']) {
259+
$this->setSectionOrder($reorder);
259260
}
260261

261262
return $this;
@@ -320,8 +321,8 @@ protected function applyPreferenceOverridesForSection($sectionNav)
320321
->filter()
321322
->each(fn ($item) => $item->isChild(false));
322323

323-
if ($sectionNav['reorder']) {
324-
$this->setSectionItemOrder($section, $sectionNav['items']);
324+
if ($reorder = $sectionNav['reorder']) {
325+
$this->setSectionItemOrder($section, $sectionNav['items'], $reorder);
325326
}
326327
}
327328

@@ -444,62 +445,76 @@ protected function renameSection($sectionKey)
444445

445446
/**
446447
* Set section order.
447-
*
448-
* @param array $sections
449448
*/
450-
protected function setSectionOrder($sections)
449+
protected function setSectionOrder(array $reorder): void
451450
{
452-
// Get conconfigured core sections...
451+
// Get unconfigured core sections...
453452
$unconfiguredCoreSections = $this->sections;
454453

455454
// Get unconfigured sections...
456-
$unconfiguredRegisteredSections = collect($this->items)->map->section()->filter()->unique();
455+
$unconfiguredRegisteredSections = collect($this->items)
456+
->mapWithKeys(fn ($item) => [NavItem::snakeCase($item->section()) => $item->section()])
457+
->filter()
458+
->unique();
457459

458-
// Merge unconfigured sections onto the end of the list and map their order...
459-
$this->sectionsOrder = collect($sections)
460-
->pluck('display')
461-
->merge($unconfiguredRegisteredSections)
460+
// Get merged unique list of sections...
461+
$order = collect()
462462
->merge($unconfiguredCoreSections)
463-
->unique()
463+
->merge($unconfiguredRegisteredSections)
464+
->unique();
465+
466+
// Reorder to match `$reorder` config...
467+
collect($reorder)
468+
->reverse()
469+
->each(fn ($key) => $order->prepend($order->pull($key), $key));
470+
471+
// Ensure `top_level` is always first...
472+
$order->prepend($order->pull('top_level'), 'top_level');
473+
474+
$this->sectionsOrder = $order
464475
->values()
465476
->mapWithKeys(fn ($section, $index) => [$section => $index + 1])
466477
->all();
467478
}
468479

469480
/**
470481
* Set section item order.
471-
*
472-
* @param string $section
473-
* @param array $items
474482
*/
475-
protected function setSectionItemOrder($section, $items)
483+
protected function setSectionItemOrder(string $section, array $items, array $reorder): void
476484
{
485+
// Get unconfigured item IDs...
486+
$unconfiguredItemIds = collect($this->items)
487+
->filter(fn ($item) => $item->section() === $section)
488+
->map
489+
->id();
490+
477491
// Generate IDs for newly created items...
478-
$itemIds = collect($items)
492+
$createdItemIds = collect($items)
479493
->map(function ($item, $id) use ($section, $items) {
480494
return $items[$id]['action'] === '@create'
481495
? $this->generateNewItemId($section, $items[$id]['display'])
482496
: $id;
483497
})
484498
->values();
485499

486-
// Get unconfigured item IDs...
487-
$unconfiguredItemIds = collect($this->items)
488-
->filter(fn ($item) => $item->section() === $section)
489-
->map
490-
->id();
491-
492500
// Merge unconfigured items into the end of the list...
493-
$itemIds = $itemIds
494-
->values()
501+
$itemIds = collect()
495502
->merge($unconfiguredItemIds)
503+
->merge($createdItemIds)
496504
->unique()
497-
->values();
505+
->flip();
506+
507+
// Reorder to match `$reorder` config...
508+
collect($reorder)
509+
->reverse()
510+
->each(fn ($key) => $itemIds->prepend($itemIds->pull($key), $key));
498511

499512
// Set an explicit order value on each item...
500513
$itemIds
514+
->flip()
501515
->map(fn ($id) => $this->findItem($id, false))
502516
->filter()
517+
->values()
503518
->each(fn ($item, $index) => $item->order($index + 1));
504519

505520
// Inform builder that section items should be ordered...
@@ -671,29 +686,29 @@ protected function userModifyItem($item, $config, $section)
671686
->reject(fn ($value, $setter) => in_array($setter, ['children', 'reorder']))
672687
->each(fn ($value, $setter) => $item->{$setter}($value));
673688

674-
if ($children = $config->get('children')) {
675-
$this->userModifyItemChildren($item, $children, $section, $config->get('reorder'));
689+
$childrenConfig = $config->get('children');
690+
$reorder = $config->get('reorder');
691+
692+
if ($childrenConfig || $reorder) {
693+
$this->userModifyItemChildren($item, $childrenConfig, $section, $reorder);
676694
}
677695

678696
return $item;
679697
}
680698

681699
/**
682700
* Modify NavItem children.
683-
*
684-
* @param \Statamic\CP\Navigation\NavItem $item
685-
* @param array $childrenOverrides
686-
* @param string $section
687-
* @return \Illuminate\Support\Collection
688701
*/
689-
protected function userModifyItemChildren($item, $childrenOverrides, $section, $reorder)
702+
protected function userModifyItemChildren(NavItem $item, ?array $childrenConfig, string $section, ?array $reorder): void
690703
{
704+
// Get original item children...
691705
$itemChildren = collect($item->original()->resolveChildren()->children())
692706
->each(fn ($item, $index) => $item->order($index + 1000))
693707
->keyBy
694708
->id();
695709

696-
collect($childrenOverrides)
710+
// Apply children preferences from config...
711+
collect($childrenConfig)
697712
->map(fn ($config, $key) => $this->userModifyChild($config, $section, $key, $item))
698713
->each(function ($item, $key) use (&$itemChildren) {
699714
$item
@@ -704,18 +719,25 @@ protected function userModifyItemChildren($item, $childrenOverrides, $section, $
704719
->values()
705720
->each(fn ($item, $index) => $item->order($index + 1));
706721

707-
$newChildren = $reorder
708-
? $itemChildren->sortBy(fn ($item) => $item->order())->values()
709-
: $itemChildren->values();
722+
// Reorder to match `$reorder` config...
723+
if ($reorder) {
724+
collect($reorder)
725+
->reverse()
726+
->each(fn ($key) => $itemChildren->prepend($itemChildren->pull($key), $key));
727+
}
710728

711-
$newChildren->each(fn ($item, $index) => $item->order($index + 1));
729+
// Update final `order`...
730+
$itemChildren
731+
->sortBy(fn ($item) => $item->order())->values()
732+
->values()
733+
->each(fn ($item, $index) => $item->order($index + 1));
734+
735+
$item->children($itemChildren, false);
712736

713737
$item->children(
714-
items: $newChildren,
738+
items: $itemChildren,
715739
generateNewIds: false,
716740
);
717-
718-
return $newChildren;
719741
}
720742

721743
/**

src/CP/Navigation/NavPreferencesNormalizer.php

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,20 @@ protected function normalize()
7373
{
7474
$navConfig = collect($this->preferences);
7575

76-
$normalized = collect()->put('reorder', (bool) $reorder = $navConfig->get('reorder', false));
77-
7876
$sections = collect($navConfig->get('sections') ?? $navConfig->except('reorder'));
7977

80-
$sections = $this
81-
->normalizeToInheritsFromReorder($sections, $reorder)
78+
$normalized = collect();
79+
80+
$sections = collect($sections)
8281
->prepend($sections->pull('top_level') ?? '@inherit', 'top_level')
83-
->map(fn ($config, $section) => $this->normalizeSectionConfig($config, $section))
84-
->reject(fn ($config) => $config['action'] === '@inherit' && ! $reorder)
85-
->map(fn ($config) => $this->removeInheritFromConfig($config))
86-
->all();
82+
->map(fn ($config, $section) => $this->normalizeSectionConfig($config, $section));
83+
84+
$normalized->put('reorder', $this->normalizeReorder(
85+
$navConfig->get('reorder', false),
86+
$sections->reject(fn ($section, $key) => $key === 'top_level'),
87+
));
8788

88-
$normalized->put('sections', $sections);
89+
$normalized->put('sections', $this->rejectInherits($sections)->all());
8990

9091
$allowedKeys = ['reorder', 'sections'];
9192

@@ -117,44 +118,26 @@ protected function normalizeSectionConfig($sectionConfig, $sectionKey)
117118

118119
$normalized->put('display', $sectionConfig->get('display', false));
119120

120-
$normalized->put('reorder', (bool) $reorder = $sectionConfig->get('reorder', false));
121-
122121
$items = collect($sectionConfig->get('items') ?? $sectionConfig->except([
123122
'action',
124123
'display',
125124
'reorder',
126125
]));
127126

128-
$items = $this
129-
->normalizeToInheritsFromReorder($items, $reorder)
127+
$items = $items
130128
->map(fn ($config, $itemId) => $this->normalizeItemConfig($itemId, $config, $sectionKey))
131129
->keyBy(fn ($config, $itemId) => $this->normalizeItemId($itemId, $config))
132-
->filter()
133-
->reject(fn ($config) => $config['action'] === '@inherit' && ! $reorder)
134-
->all();
130+
->filter();
131+
132+
$normalized->put('reorder', $this->normalizeReorder($sectionConfig->get('reorder', false), $items));
135133

136-
$normalized->put('items', $items);
134+
$normalized->put('items', $this->rejectInherits($items)->all());
137135

138136
$allowedKeys = array_merge(['action'], static::ALLOWED_NAV_SECTION_MODIFICATIONS);
139137

140138
return $normalized->only($allowedKeys)->all();
141139
}
142140

143-
/**
144-
* Remove inherit action from config.
145-
*
146-
* @param array $config
147-
* @return array
148-
*/
149-
protected function removeInheritFromConfig($config)
150-
{
151-
if ($config['action'] === '@inherit') {
152-
$config['action'] = false;
153-
}
154-
155-
return $config;
156-
}
157-
158141
/**
159142
* Normalize item config.
160143
*
@@ -190,21 +173,19 @@ protected function normalizeItemConfig($itemId, $itemConfig, $sectionKey, $remov
190173
}
191174
}
192175

193-
// Normalize `reorder` bool.
194-
if ($reorder = $normalized->get('reorder', false)) {
195-
$normalized->put('reorder', (bool) $reorder);
196-
}
197-
198176
// Normalize `children`.
199-
$children = $this
200-
->normalizeToInheritsFromReorder($normalized->get('children', []), $reorder)
177+
$children = collect($normalized->get('children', []))
201178
->map(fn ($childConfig, $childId) => $this->normalizeChildItemConfig($childId, $childConfig, $sectionKey))
202-
->keyBy(fn ($childConfig, $childId) => $this->normalizeItemId($childId, $childConfig))
203-
->all();
179+
->keyBy(fn ($childConfig, $childId) => $this->normalizeItemId($childId, $childConfig));
180+
181+
// Only output `reorder` if there are any `children`.
182+
if ($children->isNotEmpty() && $reorder = $normalized->get('reorder', false)) {
183+
$normalized->put('reorder', $this->normalizeReorder($reorder, $children));
184+
}
204185

205-
// Only output `children` in normalized output if there are any.
206-
$children
207-
? $normalized->put('children', $children)
186+
// Only output `children` if there are any.
187+
$children->isNotEmpty()
188+
? $normalized->put('children', $this->rejectInherits($children)->all())
208189
: $normalized->forget('children');
209190

210191
$allowedKeys = array_merge(['action'], static::ALLOWED_NAV_ITEM_MODIFICATIONS);
@@ -268,9 +249,7 @@ protected function itemIsModified($config)
268249
return false;
269250
}
270251

271-
$possibleModifications = array_merge(static::ALLOWED_NAV_ITEM_MODIFICATIONS);
272-
273-
return collect($possibleModifications)
252+
return collect(static::ALLOWED_NAV_ITEM_MODIFICATIONS)
274253
->intersect(array_keys($config))
275254
->isNotEmpty();
276255
}
@@ -288,18 +267,23 @@ protected function itemIsInOriginalSection($itemId, $currentSectionKey)
288267
}
289268

290269
/**
291-
* Normalize to legacy style inherits from new `reorder: []` array schema, introduced to sidestep ordering issues in SQL.
270+
* Normalize legacy `reorder: true` boolean to array, introduced to sidestep ordering issues in SQL.
292271
*/
293-
protected function normalizeToInheritsFromReorder(array|Collection $items, array|bool $reorder): Collection
272+
protected function normalizeReorder(array|bool $reorder, array|Collection $items): array|bool
294273
{
295-
if (! is_array($reorder)) {
296-
return collect($items);
274+
if ($reorder === true) {
275+
$reorder = collect($items)->keys()->all();
297276
}
298277

299-
return collect($reorder)
300-
->flip()
301-
->map(fn () => '@inherit')
302-
->merge($items);
278+
return $reorder;
279+
}
280+
281+
/**
282+
* Reject items with inherit actions.
283+
*/
284+
protected function rejectInherits(Collection $items): Collection
285+
{
286+
return $items->reject(fn ($item) => Arr::get($item, 'action') === '@inherit');
303287
}
304288

305289
/**

0 commit comments

Comments
 (0)