Passed
Push — master ( 42d03b...481eda )
by Caen
03:23 queued 12s
created

NavigationDataFactory::makeHidden()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 5
nc 13
nop 0
dl 0
loc 7
rs 8.8333
c 0
b 0
f 0
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
            || in_array($this->routeKey, Config::getArray('hyde.navigation.exclude', ['404']))
93
            || (! $this->isInstanceOf(DocumentationPage::class) && $this->pageIsInSubdirectory() && ($this->getSubdirectoryConfiguration() === 'hidden'))
94
            && (basename($this->identifier) !== 'index');
95
    }
96
97
    protected function makePriority(): int
98
    {
99
        return $this->searchForPriorityInFrontMatter()
100
            ?? $this->searchForPriorityInConfigs()
101
            ?? self::FALLBACK_PRIORITY;
102
    }
103
104
    private function searchForLabelInFrontMatter(): ?string
105
    {
106
        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...
107
            ?? $this->getMatter('navigation.title');
108
    }
109
110
    private function searchForGroupInFrontMatter(): ?string
111
    {
112
        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...
113
            ?? $this->getMatter('navigation.category');
114
    }
115
116
    private function searchForHiddenInFrontMatter(): ?bool
117
    {
118
        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...
119
            ?? $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

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