Passed
Push — master ( a6182e...4ee143 )
by Caen
03:08 queued 13s
created

AbstractPage::navigationMenuTitle()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 10
c 0
b 0
f 0
nc 8
nop 0
dl 0
loc 21
rs 9.2222
1
<?php
2
3
namespace Hyde\Framework\Contracts;
4
5
use Hyde\Framework\Actions\SourceFileParser;
6
use Hyde\Framework\Helpers\Features;
7
use Hyde\Framework\Helpers\Meta;
8
use Hyde\Framework\Hyde;
9
use Hyde\Framework\Models\FrontMatter;
10
use Hyde\Framework\Models\Pages\DocumentationPage;
11
use Hyde\Framework\Models\Pages\MarkdownPost;
12
use Hyde\Framework\Models\Route;
13
use Hyde\Framework\Services\DiscoveryService;
14
use Hyde\Framework\Services\RssFeedService;
15
use Illuminate\Support\Collection;
16
17
/**
18
 * To ensure compatibility with the Hyde Framework, all Page Models should extend this class.
19
 *
20
 * Markdown-based Pages can extend the AbstractMarkdownPage class to get relevant helpers.
21
 *
22
 * To learn about what the methods do, see the PHPDocs in the PageContract.
23
 *
24
 * @see \Hyde\Framework\Contracts\PageContract
25
 * @see \Hyde\Framework\Contracts\AbstractMarkdownPage
26
 * @test \Hyde\Framework\Testing\Feature\AbstractPageTest
27
 */
28
abstract class AbstractPage implements PageContract, CompilableContract
29
{
30
    public static string $sourceDirectory;
31
    public static string $outputDirectory;
32
    public static string $fileExtension;
33
    public static string $template;
34
35
    public string $identifier;
36
    public FrontMatter $matter;
37
38
    /** @inheritDoc */
39
    final public static function getSourceDirectory(): string
40
    {
41
        return unslash(static::$sourceDirectory);
42
    }
43
44
    /** @inheritDoc */
45
    final public static function getOutputDirectory(): string
46
    {
47
        return unslash(static::$outputDirectory);
48
    }
49
50
    /** @inheritDoc */
51
    final public static function getFileExtension(): string
52
    {
53
        return '.'.ltrim(static::$fileExtension, '.');
54
    }
55
56
    /** @inheritDoc */
57
    public static function parse(string $slug): PageContract
58
    {
59
        return (new SourceFileParser(static::class, $slug))->get();
60
    }
61
62
    /** @inheritDoc */
63
    public static function files(): array|false
64
    {
65
        return DiscoveryService::getSourceFileListForModel(static::class);
66
    }
67
68
    /** @inheritDoc */
69
    public static function all(): Collection
70
    {
71
        $collection = new Collection();
72
73
        foreach (static::files() as $basename) {
74
            $collection->push(static::parse($basename));
75
        }
76
77
        return $collection;
78
    }
79
80
    /** @inheritDoc */
81
    public static function qualifyBasename(string $basename): string
82
    {
83
        return static::getSourceDirectory().'/'.unslash($basename).static::getFileExtension();
84
    }
85
86
    /** @inheritDoc */
87
    public static function getOutputLocation(string $basename): string
88
    {
89
        // Using the trim function we ensure we don't have a leading slash when the output directory is the root directory.
90
        return trim(
91
            static::getOutputDirectory().'/'.unslash($basename),
92
            '/'
93
        ).'.html';
94
    }
95
96
    public function __construct(string $identifier = '', FrontMatter|array $matter = [])
97
    {
98
        $this->identifier = $identifier;
99
        $this->matter = $matter instanceof FrontMatter ? $matter : new FrontMatter($matter);
0 ignored issues
show
introduced by
$matter is never a sub-type of Hyde\Framework\Models\FrontMatter.
Loading history...
100
    }
101
102
    /** @interitDoc */
103
    public function __get(string $name)
104
    {
105
        return $this->matter->get($name);
106
    }
107
108
    /** @inheritDoc */
109
    public function __set(string $name, $value): void
110
    {
111
        $this->matter->set($name, $value);
112
    }
113
114
    /** @inheritDoc */
115
    public function matter(string $key = null, mixed $default = null): mixed
116
    {
117
        return $this->matter->get($key, $default);
118
    }
119
120
    /** @inheritDoc */
121
    public function getSourcePath(): string
122
    {
123
        return static::qualifyBasename($this->identifier);
124
    }
125
126
    /** @inheritDoc */
127
    public function getOutputPath(): string
128
    {
129
        return static::getCurrentPagePath().'.html';
0 ignored issues
show
Bug Best Practice introduced by
The method Hyde\Framework\Contracts...e::getCurrentPagePath() is not static, but was called statically. ( Ignorable by Annotation )

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

129
        return static::/** @scrutinizer ignore-call */ getCurrentPagePath().'.html';
Loading history...
130
    }
131
132
    /** @inheritDoc */
133
    public function getCurrentPagePath(): string
134
    {
135
        return trim(static::getOutputDirectory().'/'.$this->identifier, '/');
136
    }
137
138
    /** @inheritDoc */
139
    public function getRoute(): Route
140
    {
141
        return new Route($this);
142
    }
143
144
    /** @inheritDoc */
145
    public function htmlTitle(?string $title = null): string
146
    {
147
        $pageTitle = $title ?? $this->title ?? null;
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist on Hyde\Framework\Contracts\AbstractPage. Since you implemented __get, consider adding a @property annotation.
Loading history...
148
149
        return $pageTitle
150
            ? config('site.name', 'HydePHP').' - '.$pageTitle
151
            : config('site.name', 'HydePHP');
152
    }
153
154
    /** @inheritDoc */
155
    public function getBladeView(): string
156
    {
157
        return static::$template;
158
    }
159
160
    /** @inheritDoc */
161
    abstract public function compile(): string;
162
163
    public function getCanonicalUrl(): string
164
    {
165
        return $this->getRoute()->getQualifiedUrl();
166
    }
167
168
    /**
169
     * @return string[]
170
     *
171
     * @psalm-return list<string>
172
     */
173
    public function getDynamicMetadata(): array
174
    {
175
        $array = [];
176
177
        if ($this->canUseCanonicalUrl()) {
178
            $array[] = '<link rel="canonical" href="'.$this->getCanonicalUrl().'" />';
179
        }
180
181
        if (Features::sitemap()) {
182
            $array[] = '<link rel="sitemap" type="application/xml" title="Sitemap" href="'.Hyde::url('sitemap.xml').'" />';
183
        }
184
185
        if (Features::rss()) {
186
            $array[] = $this->makeRssFeedLink();
187
        }
188
189
        if (isset($this->title)) {
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist on Hyde\Framework\Contracts\AbstractPage. Since you implemented __get, consider adding a @property annotation.
Loading history...
190
            if ($this->hasTwitterTitleInConfig()) {
191
                $array[] = '<meta name="twitter:title" content="'.$this->htmlTitle().'" />';
192
            }
193
            if ($this->hasOpenGraphTitleInConfig()) {
194
                $array[] = '<meta property="og:title" content="'.$this->htmlTitle().'" />';
195
            }
196
        }
197
198
        if ($this instanceof MarkdownPost) {
199
            $array[] = "\n<!-- Blog Post Meta Tags -->";
200
            foreach ($this->getMetadata() as $name => $content) {
201
                $array[] = Meta::name($name, $content);
202
            }
203
            foreach ($this->getMetaProperties() as $property => $content) {
204
                $array[] = Meta::property($property, $content);
205
            }
206
        }
207
208
        return $array;
209
    }
210
211
    public function renderPageMetadata(): string
212
    {
213
        return Meta::render(
214
            withMergedData: $this->getDynamicMetadata()
215
        );
216
    }
217
218
    public function canUseCanonicalUrl(): bool
219
    {
220
        return Hyde::hasSiteUrl() && isset($this->identifier);
221
    }
222
223
    public function hasTwitterTitleInConfig(): bool
224
    {
225
        return str_contains(json_encode(config('hyde.meta', [])), 'twitter:title');
226
    }
227
228
    public function hasOpenGraphTitleInConfig(): bool
229
    {
230
        return str_contains(json_encode(config('hyde.meta', [])), 'og:title');
231
    }
232
233
    protected function makeRssFeedLink(): string
234
    {
235
        return sprintf(
236
            '<link rel="alternate" type="application/rss+xml" title="%s" href="%s" />',
237
            RssFeedService::getDescription(),
238
            Hyde::url(RssFeedService::getDefaultOutputFilename())
239
        );
240
    }
241
242
    /**
243
     * Should the item should be displayed in the navigation menu?
244
     *
245
     * @return bool
246
     */
247
    public function showInNavigation(): bool
248
    {
249
        if ($this instanceof MarkdownPost) {
250
            return false;
251
        }
252
253
        if ($this instanceof DocumentationPage) {
254
            return $this->identifier === 'index' && ! in_array('docs', config('hyde.navigation.exclude', []));
255
        }
256
257
        if ($this instanceof AbstractMarkdownPage) {
258
            if ($this->matter('navigation.hidden', false)) {
259
                return false;
260
            }
261
        }
262
263
        if (in_array($this->identifier, config('hyde.navigation.exclude', ['404']))) {
264
            return false;
265
        }
266
267
        return true;
268
    }
269
270
    /**
271
     * The relative priority, determining the position of the item in the menu.
272
     *
273
     * @return int
274
     */
275
    public function navigationMenuPriority(): int
276
    {
277
        if ($this instanceof AbstractMarkdownPage) {
278
            if ($this->matter('navigation.priority') !== null) {
279
                return $this->matter('navigation.priority');
280
            }
281
        }
282
283
        if ($this instanceof DocumentationPage) {
284
            return (int) config('hyde.navigation.order.docs', 100);
285
        }
286
287
        if ($this->identifier === 'index') {
288
            return (int) config('hyde.navigation.order.index', 0);
289
        }
290
291
        if ($this->identifier === 'posts') {
292
            return (int) config('hyde.navigation.order.posts', 10);
293
        }
294
295
        if (array_key_exists($this->identifier, config('hyde.navigation.order', []))) {
296
            return (int) config('hyde.navigation.order.'.$this->identifier);
297
        }
298
299
        return 999;
300
    }
301
302
    /**
303
     * The page title to display in the navigation menu.
304
     *
305
     * @return string
306
     */
307
    public function navigationMenuTitle(): string
308
    {
309
        if ($this instanceof AbstractMarkdownPage) {
310
            if ($this->matter('navigation.title') !== null) {
311
                return $this->matter('navigation.title');
312
            }
313
314
            if ($this->matter('title') !== null) {
315
                return $this->matter('title');
316
            }
317
        }
318
319
        if ($this->identifier === 'index') {
320
            if ($this instanceof DocumentationPage) {
321
                return config('hyde.navigation.labels.docs', 'Docs');
322
            }
323
324
            return config('hyde.navigation.labels.home', 'Home');
325
        }
326
327
        return $this->title;
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist on Hyde\Framework\Contracts\AbstractPage. Since you implemented __get, consider adding a @property annotation.
Loading history...
328
    }
329
330
    /**
331
     * Not yet implemented.
332
     *
333
     * If an item returns a route collection,
334
     * it will automatically be made into a dropdown.
335
     *
336
     * @return \Illuminate\Support\Collection<\Hyde\Framework\Models\Route>
337
     */
338
    // public function navigationMenuChildren(): Collection;
339
}
340