Passed
Push — master ( b563dc...528cd8 )
by Caen
04:09 queued 15s
created

searchForLabelInFrontMatter()   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 array_flip;
16
use function in_array;
17
use function is_a;
18
19
/**
20
 * Discover data used for navigation menus and the documentation sidebar.
21
 */
22
class NavigationDataFactory extends Concerns\PageDataFactory implements NavigationSchema
23
{
24
    /**
25
     * The front matter properties supported by this factory.
26
     *
27
     * Note that this represents a sub-schema, and is used as part of the page schema.
28
     */
29
    final public const SCHEMA = NavigationSchema::NAVIGATION_SCHEMA;
30
31
    protected const FALLBACK_PRIORITY = 999;
32
    protected const CONFIG_OFFSET = 500;
33
34
    protected readonly ?string $label;
35
    protected readonly ?string $group;
36
    protected readonly ?bool $hidden;
37
    protected readonly ?int $priority;
38
    private readonly string $title;
39
    private readonly string $routeKey;
40
    private readonly string $pageClass;
41
    private readonly string $identifier;
42
    private readonly FrontMatter $matter;
43
44
    public function __construct(CoreDataObject $pageData, string $title)
45
    {
46
        $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...
47
        $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...
48
        $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...
49
        $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...
50
        $this->title = $title;
0 ignored issues
show
Bug introduced by
The property title is declared read-only in Hyde\Framework\Factories\NavigationDataFactory.
Loading history...
51
52
        $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...
53
        $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...
54
        $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...
55
        $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...
56
    }
57
58
    /**
59
     * @return array{label: string|null, group: string|null, hidden: bool|null, priority: int|null}
60
     */
61
    public function toArray(): array
62
    {
63
        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...
64
            'label' => $this->label,
65
            'group' => $this->group,
66
            'hidden' => $this->hidden,
67
            'priority' => $this->priority,
68
        ];
69
    }
70
71
    protected function makeLabel(): ?string
72
    {
73
        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...
74
            ?? $this->searchForLabelInConfig()
75
            ?? $this->getMatter('title')
76
            ?? $this->title;
77
    }
78
79
    protected function makeGroup(): ?string
80
    {
81
        if ($this->pageIsInSubdirectory() && $this->canUseSubdirectoryForGroups()) {
82
            return $this->getSubdirectoryName();
83
        }
84
85
        return $this->searchForGroupInFrontMatter() ?? $this->defaultGroup();
86
    }
87
88
    protected function makeHidden(): bool
89
    {
90
        return $this->isInstanceOf(MarkdownPost::class)
91
            || $this->searchForHiddenInFrontMatter()
92
            || $this->isPageHiddenInNavigationConfiguration()
93
            || $this->isNonDocumentationPageInHiddenSubdirectory();
94
    }
95
96
    protected function makePriority(): int
97
    {
98
        return $this->searchForPriorityInFrontMatter()
99
            ?? $this->searchForPriorityInConfigs()
100
            ?? self::FALLBACK_PRIORITY;
101
    }
102
103
    private function searchForLabelInFrontMatter(): ?string
104
    {
105
        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...
106
            ?? $this->getMatter('navigation.title');
107
    }
108
109
    private function searchForGroupInFrontMatter(): ?string
110
    {
111
        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...
112
            ?? $this->getMatter('navigation.category');
113
    }
114
115
    private function searchForHiddenInFrontMatter(): ?bool
116
    {
117
        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...
118
            ?? $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

118
            ?? $this->invert(/** @scrutinizer ignore-type */ $this->getMatter('navigation.visible'));
Loading history...
119
    }
120
121
    private function isPageHiddenInNavigationConfiguration(): ?bool
122
    {
123
        return in_array($this->routeKey, Config::getArray('hyde.navigation.exclude', ['404']));
124
    }
125
126
    private function isNonDocumentationPageInHiddenSubdirectory(): bool
127
    {
128
        return ! $this->isInstanceOf(DocumentationPage::class)
129
            && $this->pageIsInSubdirectory()
130
            && $this->getSubdirectoryConfiguration() === 'hidden'
131
            && basename($this->identifier) !== 'index';
132
    }
133
134
    private function searchForPriorityInFrontMatter(): ?int
135
    {
136
        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...
137
            ?? $this->getMatter('navigation.order');
138
    }
139
140
    private function searchForLabelInConfig(): ?string
141
    {
142
        return Config::getArray('hyde.navigation.labels', [
143
            'index' => 'Home',
144
            DocumentationPage::homeRouteName() => 'Docs',
145
        ])[$this->routeKey] ?? null;
146
    }
147
148
    private function searchForPriorityInConfigs(): ?int
149
    {
150
        return $this->isInstanceOf(DocumentationPage::class)
151
            ? $this->searchForPriorityInSidebarConfig()
152
            : $this->searchForPriorityInNavigationConfig();
153
    }
154
155
    private function searchForPriorityInSidebarConfig(): ?int
156
    {
157
        // Sidebars uses a special syntax where the keys are just the page identifiers in a flat array.
158
        // TODO: In the future we could normalize this with the standard navigation config so both strategies can be auto-detected and used.
159
160
        // Adding an offset makes so that pages with a front matter priority that is lower can be shown first.
161
        // This is all to make it easier to mix ways of adding priorities.
162
163
        return $this->offset(
164
            array_flip(Config::getArray('docs.sidebar_order', []))[$this->identifier] ?? null,
165
            self::CONFIG_OFFSET
166
        );
167
    }
168
169
    private function searchForPriorityInNavigationConfig(): ?int
170
    {
171
        return Config::getArray('hyde.navigation.order', [
172
            'index' => 0,
173
            'posts' => 10,
174
            'docs/index' => 100,
175
        ])[$this->routeKey] ?? null;
176
    }
177
178
    private function canUseSubdirectoryForGroups(): bool
179
    {
180
        return $this->getSubdirectoryConfiguration() === 'dropdown'
181
            || $this->isInstanceOf(DocumentationPage::class);
182
    }
183
184
    private function defaultGroup(): ?string
185
    {
186
        return $this->isInstanceOf(DocumentationPage::class) ? 'other' : null;
187
    }
188
189
    private function pageIsInSubdirectory(): bool
190
    {
191
        return Str::contains($this->identifier, '/');
192
    }
193
194
    private function getSubdirectoryName(): string
195
    {
196
        return Str::before($this->identifier, '/');
197
    }
198
199
    protected function getSubdirectoryConfiguration(): string
200
    {
201
        return Config::getString('hyde.navigation.subdirectories', 'hidden');
202
    }
203
204
    protected function isInstanceOf(string $class): bool
205
    {
206
        return is_a($this->pageClass, $class, true);
207
    }
208
209
    protected function invert(?bool $value): ?bool
210
    {
211
        return $value === null ? null : ! $value;
212
    }
213
214
    protected function offset(?int $value, int $offset): ?int
215
    {
216
        return $value === null ? null : $value + $offset;
217
    }
218
219
    protected function getMatter(string $key): string|null|int|bool
220
    {
221
        return $this->matter->get($key);
222
    }
223
}
224