Passed
Push — master ( 96b123...ed14ee )
by Caen
03:52 queued 13s
created

searchForGroupInFrontMatter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Framework\Factories;
6
7
use Hyde\Facades\Config;
8
use Illuminate\Support\Str;
9
use Hyde\Pages\MarkdownPost;
10
use Hyde\Pages\DocumentationPage;
11
use Hyde\Markdown\Models\FrontMatter;
12
use Hyde\Framework\Factories\Concerns\CoreDataObject;
13
use Hyde\Markdown\Contracts\FrontMatter\SubSchemas\NavigationSchema;
14
15
use function basename;
16
use function array_flip;
17
use function in_array;
18
use function is_a;
19
use function array_key_exists;
20
21
/**
22
 * Discover data used for navigation menus and the documentation sidebar.
23
 */
24
class NavigationDataFactory extends Concerns\PageDataFactory implements NavigationSchema
25
{
26
    /**
27
     * The front matter properties supported by this factory.
28
     *
29
     * Note that this represents a sub-schema, and is used as part of the page schema.
30
     */
31
    final public const SCHEMA = NavigationSchema::NAVIGATION_SCHEMA;
32
33
    protected const FALLBACK_PRIORITY = 999;
34
    protected const CONFIG_OFFSET = 500;
35
36
    protected readonly ?string $label;
37
    protected readonly ?string $group;
38
    protected readonly ?bool $hidden;
39
    protected readonly ?int $priority;
40
    private readonly string $title;
41
    private readonly string $routeKey;
42
    private readonly string $pageClass;
43
    private readonly string $identifier;
44
    private readonly FrontMatter $matter;
45
46
    public function __construct(CoreDataObject $pageData, string $title)
47
    {
48
        $this->matter = $pageData->matter;
0 ignored issues
show
Bug introduced by
The property matter is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
49
        $this->identifier = $pageData->identifier;
0 ignored issues
show
Bug introduced by
The property identifier is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
50
        $this->pageClass = $pageData->pageClass;
0 ignored issues
show
Bug introduced by
The property pageClass is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
51
        $this->routeKey = $pageData->routeKey;
0 ignored issues
show
Bug introduced by
The property routeKey is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
52
        $this->title = $title;
0 ignored issues
show
Bug introduced by
The property title is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
53
54
        $this->label = $this->makeLabel();
0 ignored issues
show
Bug introduced by
The property label is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
55
        $this->group = $this->makeGroup();
0 ignored issues
show
Bug introduced by
The property group is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
56
        $this->hidden = $this->makeHidden();
0 ignored issues
show
Bug introduced by
The property hidden is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
57
        $this->priority = $this->makePriority();
0 ignored issues
show
Bug introduced by
The property priority is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
58
    }
59
60
    /**
61
     * @return array{label: string|null, group: string|null, hidden: bool|null, priority: int|null}
62
     */
63
    public function toArray(): array
64
    {
65
        return [
0 ignored issues
show
introduced by
The expression return array('label' => ...ty' => $this->priority) returns an array which contains values of type boolean|integer|string which are incompatible with the return type Illuminate\Contracts\Support\TValue mandated by Illuminate\Contracts\Support\Arrayable::toArray().
Loading history...
66
            'label' => $this->label,
67
            'group' => $this->group,
68
            'hidden' => $this->hidden,
69
            'priority' => $this->priority,
70
        ];
71
    }
72
73
    protected function makeLabel(): ?string
74
    {
75
        return $this->searchForLabelInFrontMatter()
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->searchForL...title') ?? $this->title could return the type boolean which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
76
            ?? $this->searchForLabelInConfig()
77
            ?? $this->getMatter('title')
78
            ?? $this->title;
79
    }
80
81
    protected function makeGroup(): ?string
82
    {
83
        if ($this->pageIsInSubdirectory() && $this->canUseSubdirectoryForGroups()) {
84
            return $this->getSubdirectoryName();
85
        }
86
87
        return $this->searchForGroupInFrontMatter() ?? $this->defaultGroup();
88
    }
89
90
    protected function makeHidden(): bool
91
    {
92
        return $this->isInstanceOf(MarkdownPost::class)
93
            || $this->searchForHiddenInFrontMatter()
94
            || $this->isPageHiddenInNavigationConfiguration()
95
            || $this->isNonDocumentationPageInHiddenSubdirectory();
96
    }
97
98
    protected function makePriority(): int
99
    {
100
        return $this->searchForPriorityInFrontMatter()
101
            ?? $this->searchForPriorityInConfigs()
102
            ?? self::FALLBACK_PRIORITY;
103
    }
104
105
    private function searchForLabelInFrontMatter(): ?string
106
    {
107
        return $this->getMatter('navigation.label')
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMatter(...ter('navigation.title') could return the type boolean which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
108
            ?? $this->getMatter('navigation.title');
109
    }
110
111
    private function searchForGroupInFrontMatter(): ?string
112
    {
113
        return $this->getMatter('navigation.group')
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMatter(...('navigation.category') could return the type boolean which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
114
            ?? $this->getMatter('navigation.category');
115
    }
116
117
    private function searchForHiddenInFrontMatter(): ?bool
118
    {
119
        return $this->getMatter('navigation.hidden')
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMatter(...('navigation.visible')) could return the type integer|string which is incompatible with the type-hinted return boolean|null. Consider adding an additional type-check to rule them out.
Loading history...
120
            ?? $this->invert($this->getMatter('navigation.visible'));
0 ignored issues
show
Bug introduced by
It seems like $this->getMatter('navigation.visible') can also be of type integer and string; however, parameter $value of Hyde\Framework\Factories...onDataFactory::invert() does only seem to accept boolean|null, 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

120
            ?? $this->invert(/** @scrutinizer ignore-type */ $this->getMatter('navigation.visible'));
Loading history...
121
    }
122
123
    private function isPageHiddenInNavigationConfiguration(): ?bool
124
    {
125
        return in_array($this->routeKey, Config::getArray('hyde.navigation.exclude', ['404']));
126
    }
127
128
    private function isNonDocumentationPageInHiddenSubdirectory(): bool
129
    {
130
        return ! $this->isInstanceOf(DocumentationPage::class)
131
            && $this->pageIsInSubdirectory()
132
            && $this->getSubdirectoryConfiguration() === 'hidden'
133
            && basename($this->identifier) !== 'index';
134
    }
135
136
    private function searchForPriorityInFrontMatter(): ?int
137
    {
138
        return $this->getMatter('navigation.priority')
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getMatter(...ter('navigation.order') could return the type boolean|string which is incompatible with the type-hinted return integer|null. Consider adding an additional type-check to rule them out.
Loading history...
139
            ?? $this->getMatter('navigation.order');
140
    }
141
142
    private function searchForLabelInConfig(): ?string
143
    {
144
        /** @var array<string, string> $config */
145
        $config = Config::getArray('hyde.navigation.labels', [
146
            'index' => 'Home',
147
            DocumentationPage::homeRouteName() => 'Docs',
148
        ]);
149
150
        return $config[$this->routeKey] ?? null;
151
    }
152
153
    private function searchForPriorityInConfigs(): ?int
154
    {
155
        return $this->isInstanceOf(DocumentationPage::class)
156
            ? $this->searchForPriorityInSidebarConfig()
157
            : $this->searchForPriorityInNavigationConfig();
158
    }
159
160
    private function searchForPriorityInSidebarConfig(): ?int
161
    {
162
        /** @var array<string>|array<string, int> $config */
163
        $config = Config::getArray('docs.sidebar_order', []);
164
165
        return $this->parseNavigationPriorityConfig($config);
166
    }
167
168
    private function searchForPriorityInNavigationConfig(): ?int
169
    {
170
        /** @var array<string, int>|array<string> $config */
171
        $config = Config::getArray('hyde.navigation.order', [
172
            'index' => 0,
173
            'posts' => 10,
174
            'docs/index' => 100,
175
        ]);
176
177
        return $this->parseNavigationPriorityConfig($config);
178
    }
179
180
    /** @param array<string, int>|array<string> $config */
181
    private function parseNavigationPriorityConfig(array $config): ?int
182
    {
183
        $pageKey = $this->isInstanceOf(DocumentationPage::class)
184
            ? $this->identifier // Required for backwards compatibility.
185
            : $this->routeKey;
186
187
        // Check if the config entry is a flat array or a keyed array.
188
        if (! array_key_exists($pageKey, $config)) {
189
            // Adding an offset makes so that pages with a front matter priority, or
190
            // explicit keyed priority selection that is lower can be shown first.
191
            // This is all to make it easier to mix ways of adding priorities.
192
193
            return $this->offset(
194
                array_flip($config)[$pageKey] ?? null,
195
                self::CONFIG_OFFSET
196
            );
197
        }
198
199
        return $config[$pageKey] ?? null;
200
    }
201
202
    private function canUseSubdirectoryForGroups(): bool
203
    {
204
        return $this->getSubdirectoryConfiguration() === 'dropdown'
205
            || $this->isInstanceOf(DocumentationPage::class);
206
    }
207
208
    private function defaultGroup(): ?string
209
    {
210
        return $this->isInstanceOf(DocumentationPage::class) ? 'other' : null;
211
    }
212
213
    private function pageIsInSubdirectory(): bool
214
    {
215
        return Str::contains($this->identifier, '/');
216
    }
217
218
    private function getSubdirectoryName(): string
219
    {
220
        return Str::before($this->identifier, '/');
221
    }
222
223
    protected function getSubdirectoryConfiguration(): string
224
    {
225
        return Config::getString('hyde.navigation.subdirectories', 'hidden');
226
    }
227
228
    /** @param class-string $class */
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
229
    protected function isInstanceOf(string $class): bool
230
    {
231
        return is_a($this->pageClass, $class, true);
232
    }
233
234
    protected function invert(?bool $value): ?bool
235
    {
236
        return $value === null ? null : ! $value;
237
    }
238
239
    protected function offset(?int $value, int $offset): ?int
240
    {
241
        return $value === null ? null : $value + $offset;
242
    }
243
244
    protected function getMatter(string $key): string|null|int|bool
245
    {
246
        return $this->matter->get($key);
247
    }
248
}
249