NavigationMenuGenerator::getOrCreateGroupItem()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 1
dl 0
loc 20
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Framework\Features\Navigation;
6
7
use Hyde\Hyde;
8
use Hyde\Facades\Config;
9
use Hyde\Support\Models\Route;
10
use Hyde\Pages\DocumentationPage;
11
use Illuminate\Support\Collection;
12
use Hyde\Foundation\Facades\Routes;
13
use Hyde\Foundation\Kernel\RouteCollection;
14
use Hyde\Framework\Exceptions\InvalidConfigurationException;
15
16
use function filled;
17
use function assert;
18
use function collect;
19
use function in_array;
20
use function strtolower;
21
22
class NavigationMenuGenerator
23
{
24
    /** @var \Illuminate\Support\Collection<string, \Hyde\Framework\Features\Navigation\NavigationItem|\Hyde\Framework\Features\Navigation\NavigationGroup> */
25
    protected Collection $items;
26
27
    /** @var \Hyde\Foundation\Kernel\RouteCollection<string, \Hyde\Support\Models\Route> */
28
    protected RouteCollection $routes;
29
30
    /** @var class-string<\Hyde\Framework\Features\Navigation\NavigationMenu> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<\Hyde\Frame...igation\NavigationMenu> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<\Hyde\Framework\Features\Navigation\NavigationMenu>.
Loading history...
31
    protected string $menuType;
32
33
    protected bool $generatesSidebar;
34
    protected bool $usesGroups;
35
36
    /** @param class-string<\Hyde\Framework\Features\Navigation\NavigationMenu> $menuType */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<\Hyde\Frame...igation\NavigationMenu> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<\Hyde\Framework\Features\Navigation\NavigationMenu>.
Loading history...
37
    protected function __construct(string $menuType)
38
    {
39
        assert(in_array($menuType, [MainNavigationMenu::class, DocumentationSidebar::class]));
40
41
        $this->menuType = $menuType;
42
43
        $this->items = new Collection();
44
45
        $this->generatesSidebar = $menuType === DocumentationSidebar::class;
46
47
        $this->routes = $this->generatesSidebar
48
            ? Routes::getRoutes(DocumentationPage::class)
0 ignored issues
show
Bug introduced by
The method getRoutes() does not exist on Hyde\Foundation\Facades\Routes. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

48
            ? Routes::/** @scrutinizer ignore-call */ getRoutes(DocumentationPage::class)
Loading history...
49
            : Routes::all();
50
51
        $this->usesGroups = $this->usesGroups();
52
    }
53
54
    /** @param class-string<\Hyde\Framework\Features\Navigation\NavigationMenu> $menuType */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<\Hyde\Frame...igation\NavigationMenu> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<\Hyde\Framework\Features\Navigation\NavigationMenu>.
Loading history...
55
    public static function handle(string $menuType): MainNavigationMenu|DocumentationSidebar
56
    {
57
        $menu = new static($menuType);
58
59
        $menu->generate();
60
61
        return new $menuType($menu->items);
62
    }
63
64
    protected function generate(): void
65
    {
66
        $this->routes->each(function (Route $route): void {
67
            if ($this->canAddRoute($route)) {
68
                if ($this->canGroupRoute($route)) {
69
                    $this->addRouteToGroup($route);
70
                } else {
71
                    $this->items->put($route->getRouteKey(), NavigationItem::create($route));
0 ignored issues
show
Bug introduced by
$route->getRouteKey() of type string is incompatible with the type Illuminate\Support\TKey expected by parameter $key of Illuminate\Support\Collection::put(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

71
                    $this->items->put(/** @scrutinizer ignore-type */ $route->getRouteKey(), NavigationItem::create($route));
Loading history...
72
                }
73
            }
74
        });
75
76
        if ($this->generatesSidebar) {
77
            // If there are no pages other than the index page, we add it to the sidebar so that it's not empty
78
            if ($this->items->count() === 0 && DocumentationPage::home() !== null) {
79
                $this->items->push(NavigationItem::create(DocumentationPage::home()));
0 ignored issues
show
Bug introduced by
It seems like Hyde\Pages\DocumentationPage::home() can also be of type null; however, parameter $destination of Hyde\Framework\Features\...avigationItem::create() does only seem to accept Hyde\Support\Models\Route|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

79
                $this->items->push(NavigationItem::create(/** @scrutinizer ignore-type */ DocumentationPage::home()));
Loading history...
80
            }
81
        } else {
82
            collect(Config::getArray('hyde.navigation.custom', []))->each(function (array $data): void {
0 ignored issues
show
Bug introduced by
Hyde\Facades\Config::get...ation.custom', array()) of type array is incompatible with the type Illuminate\Contracts\Support\Arrayable expected by parameter $value of collect(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

82
            collect(/** @scrutinizer ignore-type */ Config::getArray('hyde.navigation.custom', []))->each(function (array $data): void {
Loading history...
83
                /** @var array{destination: string, label: ?string, priority: ?int, attributes: array<string, scalar>} $data */
84
                $message = 'Invalid navigation item configuration detected the configuration file. Please double check the syntax.';
85
                $item = InvalidConfigurationException::try(fn () => NavigationItem::create(...$data), $message);
0 ignored issues
show
Bug introduced by
$data is expanded, but the parameter $destination of Hyde\Framework\Features\...avigationItem::create() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

85
                $item = InvalidConfigurationException::try(fn () => NavigationItem::create(/** @scrutinizer ignore-type */ ...$data), $message);
Loading history...
86
87
                // Since these were added explicitly by the user, we can assume they should always be shown
88
                $this->items->push($item);
89
            });
90
        }
91
    }
92
93
    protected function usesGroups(): bool
94
    {
95
        if ($this->generatesSidebar) {
96
            // In order to know if we should use groups in the sidebar, we need to loop through the pages and see if they have a group set.
97
            // This automatically enables the sidebar grouping for all pages if at least one group is set.
98
99
            return $this->routes->first(fn (Route $route): bool => filled($route->getPage()->navigationMenuGroup())) !== null;
100
        } else {
101
            return Config::getString('hyde.navigation.subdirectory_display', 'hidden') === 'dropdown';
102
        }
103
    }
104
105
    protected function canAddRoute(Route $route): bool
106
    {
107
        if (! $route->getPage()->showInNavigation()) {
108
            return false;
109
        }
110
111
        if ($this->generatesSidebar) {
112
            // Since the index page is linked in the header, we don't want it in the sidebar
113
            return ! $route->is(DocumentationPage::homeRouteName());
114
        } else {
115
            // While we for the most part can rely on the navigation visibility state provided by the navigation data factory,
116
            // we need to make an exception for documentation pages, which generally have a visible state, as the data is
117
            // also used in the sidebar. But we only want the documentation index page to be in the main navigation.
118
            return ! $route->getPage() instanceof DocumentationPage || $route->is(DocumentationPage::homeRouteName());
119
        }
120
    }
121
122
    protected function canGroupRoute(Route $route): bool
123
    {
124
        if (! $this->generatesSidebar) {
125
            return $route->getPage()->navigationMenuGroup() !== null;
126
        }
127
128
        if (! $this->usesGroups) {
129
            return false;
130
        }
131
132
        return true;
133
    }
134
135
    protected function addRouteToGroup(Route $route): void
136
    {
137
        $item = NavigationItem::create($route);
138
139
        $groupKey = $item->getPage()->navigationMenuGroup();
140
        $groupName = $this->generatesSidebar ? ($groupKey ?? 'Other') : $groupKey;
141
142
        $groupItem = $this->getOrCreateGroupItem($groupName);
0 ignored issues
show
Bug introduced by
It seems like $groupName can also be of type null; however, parameter $groupName of Hyde\Framework\Features\...:getOrCreateGroupItem() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
        $groupItem = $this->getOrCreateGroupItem(/** @scrutinizer ignore-type */ $groupName);
Loading history...
143
144
        $groupItem->add($item);
145
146
        if (! $this->items->has($groupItem->getGroupKey())) {
147
            $this->items->put($groupItem->getGroupKey(), $groupItem);
0 ignored issues
show
Bug introduced by
$groupItem->getGroupKey() of type string is incompatible with the type Illuminate\Support\TKey expected by parameter $key of Illuminate\Support\Collection::put(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

147
            $this->items->put(/** @scrutinizer ignore-type */ $groupItem->getGroupKey(), $groupItem);
Loading history...
148
        }
149
    }
150
151
    protected function getOrCreateGroupItem(string $groupName): NavigationGroup
152
    {
153
        $groupKey = NavigationGroup::normalizeGroupKey($groupName);
154
        $group = $this->items->get($groupKey);
155
156
        if ($group instanceof NavigationGroup) {
157
            return $group;
158
        } elseif ($group instanceof NavigationItem) {
159
            // We are trying to add children to an existing navigation menu item,
160
            // so here we create a new instance to replace the base one, this
161
            // does mean we lose the destination as we can't link to them.
162
163
            $item = new NavigationGroup($group->getLabel(), [], $group->getPriority());
164
165
            $this->items->put($groupKey, $item);
0 ignored issues
show
Bug introduced by
$groupKey of type string is incompatible with the type Illuminate\Support\TKey expected by parameter $key of Illuminate\Support\Collection::put(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
            $this->items->put(/** @scrutinizer ignore-type */ $groupKey, $item);
Loading history...
166
167
            return $item;
168
        }
169
170
        return $this->createGroupItem($groupKey, $groupName);
171
    }
172
173
    protected function createGroupItem(string $groupKey, string $groupName): NavigationGroup
174
    {
175
        $label = $this->searchForGroupLabelInConfig($groupKey) ?? $groupName;
176
177
        $priority = $this->searchForGroupPriorityInConfig($groupKey);
178
179
        return NavigationGroup::create($this->normalizeGroupLabel($label), [], $priority ?? NavigationMenu::LAST);
180
    }
181
182
    protected function normalizeGroupLabel(string $label): string
183
    {
184
        // If there is no label, and the group is a slug, we can make a title from it
185
        if ($label === strtolower($label)) {
186
            return Hyde::makeTitle($label);
0 ignored issues
show
Bug introduced by
The method makeTitle() does not exist on Hyde\Hyde. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
            return Hyde::/** @scrutinizer ignore-call */ makeTitle($label);
Loading history...
187
        }
188
189
        return $label;
190
    }
191
192
    protected function searchForGroupLabelInConfig(string $groupKey): ?string
193
    {
194
        return $this->getConfigArray($this->generatesSidebar ? 'docs.sidebar.labels' : 'hyde.navigation.labels')[$groupKey] ?? null;
195
    }
196
197
    protected function searchForGroupPriorityInConfig(string $groupKey): ?int
198
    {
199
        return $this->getConfigArray($this->generatesSidebar ? 'docs.sidebar.order' : 'hyde.navigation.order')[$groupKey] ?? null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getConfigA...er')[$groupKey] ?? null could return the type string which is incompatible with the type-hinted return integer|null. Consider adding an additional type-check to rule them out.
Loading history...
200
    }
201
202
    /** @return array<string|int, string|int> */
203
    protected function getConfigArray(string $key): array
204
    {
205
        /** @var array<string|int, string|int> $array */
206
        $array = Config::getArray($key, []);
207
208
        return $array;
209
    }
210
}
211