HydePage::getRoute()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Pages\Concerns;
6
7
use Hyde\Hyde;
8
use Hyde\Facades\Config;
9
use Hyde\Foundation\Facades;
10
use Hyde\Foundation\Facades\Files;
11
use Hyde\Foundation\Facades\Pages;
12
use Hyde\Foundation\Facades\Routes;
13
use Hyde\Foundation\Kernel\PageCollection;
14
use Hyde\Framework\Actions\SourceFileParser;
15
use Hyde\Framework\Concerns\InteractsWithFrontMatter;
16
use Hyde\Framework\Factories\Concerns\HasFactory;
17
use Hyde\Framework\Features\Metadata\PageMetadataBag;
18
use Hyde\Framework\Features\Navigation\NavigationData;
19
use Hyde\Markdown\Contracts\FrontMatter\PageSchema;
20
use Hyde\Markdown\Models\FrontMatter;
21
use Hyde\Support\Concerns\Serializable;
22
use Hyde\Support\Contracts\SerializableContract;
23
use Hyde\Support\Filesystem\SourceFile;
24
use Hyde\Support\Models\Route;
25
use Hyde\Support\Models\RouteKey;
26
use Illuminate\Support\Str;
27
28
use function Hyde\unslash;
29
use function filled;
30
use function ltrim;
31
use function rtrim;
32
33
/**
34
 * The base class for all Hyde pages.
35
 *
36
 * To ensure compatibility with the Hyde Framework, all page models should extend this class.
37
 * Markdown-based pages can extend the BaseMarkdownPage class to get relevant helpers.
38
 *
39
 * Unlike other frameworks, in general you don't instantiate pages yourself in Hyde,
40
 * instead, the page models acts as blueprints defining information for Hyde to
41
 * know how to parse a file, and what data around it should be generated.
42
 *
43
 * To create a parsed file instance, you'd typically just create a source file,
44
 * and you can then access the parsed file from the HydeKernel's page index.
45
 * The source files are usually parsed by the SourceFileParser action.
46
 *
47
 * In Blade views, you can always access the current page instance being rendered using the $page variable.
48
 *
49
 * @see \Hyde\Pages\Concerns\BaseMarkdownPage
50
 */
51
abstract class HydePage implements PageSchema, SerializableContract
52
{
53
    use InteractsWithFrontMatter;
54
    use Serializable;
55
    use HasFactory;
0 ignored issues
show
Bug introduced by
The trait Hyde\Framework\Factories\Concerns\HasFactory requires the property $markdown which is not provided by Hyde\Pages\Concerns\HydePage.
Loading history...
56
57
    public static string $sourceDirectory;
58
    public static string $outputDirectory;
59
    public static string $fileExtension;
60
    public static string $template;
61
62
    public readonly string $identifier;
63
    public readonly string $routeKey;
64
    public readonly string $title;
65
66
    public FrontMatter $matter;
67
    public PageMetadataBag $metadata;
68
    public NavigationData $navigation;
69
70
    /**
71
     * Create a new page instance. Static alias for the constructor.
72
     */
73
    public static function make(string $identifier = '', FrontMatter|array $matter = []): static
74
    {
75
        return new static($identifier, $matter);
76
    }
77
78
    /**
79
     * Construct a new page instance.
80
     */
81
    public function __construct(string $identifier = '', FrontMatter|array $matter = [])
82
    {
83
        $this->identifier = $identifier;
0 ignored issues
show
Bug introduced by
The property identifier is declared read-only in Hyde\Pages\Concerns\HydePage.
Loading history...
84
        $this->routeKey = RouteKey::fromPage(static::class, $identifier)->get();
0 ignored issues
show
Bug introduced by
The property routeKey is declared read-only in Hyde\Pages\Concerns\HydePage.
Loading history...
85
        $this->matter = $matter instanceof FrontMatter ? $matter : new FrontMatter($matter);
0 ignored issues
show
introduced by
$matter is never a sub-type of Hyde\Markdown\Models\FrontMatter.
Loading history...
86
87
        $this->constructFactoryData();
88
        $this->constructMetadata();
89
    }
90
91
    // Section: State
92
93
    /**
94
     * Returns whether the page type is discoverable through auto-discovery.
95
     */
96
    public static function isDiscoverable(): bool
97
    {
98
        return isset(static::$sourceDirectory, static::$outputDirectory, static::$fileExtension) && filled(static::$sourceDirectory);
99
    }
100
101
    // Section: Query
102
103
    /**
104
     * Get a page instance from the Kernel's page index by its identifier.
105
     *
106
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the page does not exist.
107
     */
108
    public static function get(string $identifier): static
109
    {
110
        return Pages::getPage(static::sourcePath($identifier));
0 ignored issues
show
Bug introduced by
The method getPage() does not exist on Hyde\Foundation\Facades\Pages. 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

110
        return Pages::/** @scrutinizer ignore-call */ getPage(static::sourcePath($identifier));
Loading history...
111
    }
112
113
    /**
114
     * Parse a source file into a new page model instance.
115
     *
116
     * @param  string  $identifier  The identifier of the page to parse.
117
     * @return static New page model instance for the parsed source file.
118
     *
119
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the file does not exist.
120
     */
121
    public static function parse(string $identifier): static
122
    {
123
        return (new SourceFileParser(static::class, $identifier))->get();
124
    }
125
126
    /**
127
     * Get an array of all the source file identifiers for the model.
128
     *
129
     * Note that the values do not include the source directory or file extension.
130
     *
131
     * @return array<string>
132
     */
133
    public static function files(): array
134
    {
135
        return Files::getFiles(static::class)->map(function (SourceFile $file): string {
0 ignored issues
show
Bug introduced by
The method getFiles() does not exist on Hyde\Foundation\Facades\Files. 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

135
        return Files::/** @scrutinizer ignore-call */ getFiles(static::class)->map(function (SourceFile $file): string {
Loading history...
136
            return static::pathToIdentifier($file->getPath());
137
        })->values()->toArray();
138
    }
139
140
    /**
141
     * Get a collection of all pages, parsed into page models.
142
     *
143
     * @return \Hyde\Foundation\Kernel\PageCollection<static>
144
     */
145
    public static function all(): PageCollection
146
    {
147
        return Facades\Pages::getPages(static::class);
0 ignored issues
show
Bug introduced by
The method getPages() does not exist on Hyde\Foundation\Facades\Pages. 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

147
        return Facades\Pages::/** @scrutinizer ignore-call */ getPages(static::class);
Loading history...
148
    }
149
150
    // Section: Filesystem
151
152
    /**
153
     * Get the directory where source files are stored for the page type.
154
     */
155
    public static function sourceDirectory(): string
156
    {
157
        return static::$sourceDirectory ?? Hyde::getSourceRoot();
0 ignored issues
show
Bug introduced by
The method getSourceRoot() 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

157
        return static::$sourceDirectory ?? Hyde::/** @scrutinizer ignore-call */ getSourceRoot();
Loading history...
158
    }
159
160
    /**
161
     * Get the output subdirectory to store compiled HTML files for the page type.
162
     */
163
    public static function outputDirectory(): string
164
    {
165
        return static::$outputDirectory ?? '';
166
    }
167
168
    /**
169
     * Get the file extension of the source files for the page type.
170
     */
171
    public static function fileExtension(): string
172
    {
173
        return static::$fileExtension ?? '';
174
    }
175
176
    /**
177
     * Set the output directory for the page type.
178
     */
179
    public static function setSourceDirectory(string $sourceDirectory): void
180
    {
181
        static::$sourceDirectory = unslash($sourceDirectory);
182
    }
183
184
    /**
185
     * Set the source directory for the page type.
186
     */
187
    public static function setOutputDirectory(string $outputDirectory): void
188
    {
189
        static::$outputDirectory = unslash($outputDirectory);
190
    }
191
192
    /**
193
     * Set the file extension for the page type.
194
     */
195
    public static function setFileExtension(string $fileExtension): void
196
    {
197
        static::$fileExtension = rtrim('.'.ltrim($fileExtension, '.'), '.');
198
    }
199
200
    /**
201
     * Qualify a page identifier into file path to the source file, relative to the project root.
202
     */
203
    public static function sourcePath(string $identifier): string
204
    {
205
        return unslash(static::sourceDirectory().'/'.unslash($identifier).static::fileExtension());
206
    }
207
208
    /**
209
     * Qualify a page identifier into a target output file path, relative to the _site output directory.
210
     */
211
    public static function outputPath(string $identifier): string
212
    {
213
        return RouteKey::fromPage(static::class, $identifier).'.html';
214
    }
215
216
    /**
217
     * Get an absolute file path to the page's source directory, or a file within it.
218
     */
219
    public static function path(string $path = ''): string
220
    {
221
        return Hyde::path(unslash(static::sourceDirectory().'/'.unslash($path)));
0 ignored issues
show
Bug introduced by
The method path() 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

221
        return Hyde::/** @scrutinizer ignore-call */ path(unslash(static::sourceDirectory().'/'.unslash($path)));
Loading history...
222
    }
223
224
    /**
225
     * Format a filename to an identifier for a given model. Unlike the basename function, any nested paths
226
     * within the source directory are retained in order to satisfy the page identifier definition.
227
     *
228
     * @param  string  $path  Example: index.blade.php
229
     * @return string Example: index
230
     */
231
    public static function pathToIdentifier(string $path): string
232
    {
233
        return unslash(Str::between(Hyde::pathToRelative($path),
0 ignored issues
show
Bug introduced by
The method pathToRelative() 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

233
        return unslash(Str::between(Hyde::/** @scrutinizer ignore-call */ pathToRelative($path),
Loading history...
234
            static::sourceDirectory().'/',
235
            static::fileExtension())
236
        );
237
    }
238
239
    /**
240
     * Get the route key base for the page model.
241
     *
242
     * This is the same value as the output directory.
243
     */
244
    public static function baseRouteKey(): string
245
    {
246
        return static::outputDirectory();
247
    }
248
249
    /**
250
     * Compile the page into static HTML.
251
     *
252
     * @return string The compiled HTML for the page.
253
     */
254
    abstract public function compile(): string;
255
256
    /**
257
     * Get the instance as an array.
258
     */
259
    public function toArray(): array
260
    {
261
        return [
262
            'class' => static::class,
263
            'identifier' => $this->identifier,
264
            'routeKey' => $this->routeKey,
265
            'matter' => $this->matter,
266
            'metadata' => $this->metadata,
267
            'navigation' => $this->navigation,
268
            'title' => $this->title,
269
        ];
270
    }
271
272
    /**
273
     * Get the path to the instance source file, relative to the project root.
274
     */
275
    public function getSourcePath(): string
276
    {
277
        return unslash(static::sourcePath($this->identifier));
278
    }
279
280
    /**
281
     * Get the path where the compiled page will be saved.
282
     *
283
     * @return string Path relative to the site output directory.
284
     */
285
    public function getOutputPath(): string
286
    {
287
        return unslash(static::outputPath($this->identifier));
288
    }
289
290
    // Section: Routing
291
292
    /**
293
     * Get the route key for the page.
294
     *
295
     * The route key is the page URL path, relative to the site root, but without any file extensions.
296
     * For example, if the page will be saved to `_site/docs/index.html`, the key is `docs/index`.
297
     *
298
     * Route keys are used to identify page routes, similar to how named routes work in Laravel,
299
     * only that here the name is not just arbitrary, but also defines the output location,
300
     * as the route key is used to determine the output path which is `$routeKey.html`.
301
     */
302
    public function getRouteKey(): string
303
    {
304
        return $this->routeKey;
305
    }
306
307
    /**
308
     * Get the route object for the page.
309
     */
310
    public function getRoute(): Route
311
    {
312
        return Routes::find($this->getRouteKey()) ?? new Route($this);
313
    }
314
315
    /**
316
     * Format the page instance to a URL path, with support for pretty URLs if enabled.
317
     *
318
     * Note that the link is always relative to site root, and does not contain `../` segments.
319
     */
320
    public function getLink(): string
321
    {
322
        return Hyde::formatLink($this->getOutputPath());
0 ignored issues
show
Bug introduced by
The method formatLink() 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

322
        return Hyde::/** @scrutinizer ignore-call */ formatLink($this->getOutputPath());
Loading history...
323
    }
324
325
    // Section: Getters
326
327
    /**
328
     * Get the page model's identifier property.
329
     *
330
     * The identifier is the part between the source directory and the file extension.
331
     * It may also be known as a 'slug', or previously 'basename', but it retains
332
     * the nested path structure if the page is stored in a subdirectory.
333
     *
334
     * For example, the identifier of a source file stored as '_pages/about/contact.md'
335
     * would be 'about/contact', and 'pages/about.md' would simply be 'about'.
336
     */
337
    public function getIdentifier(): string
338
    {
339
        return $this->identifier;
340
    }
341
342
    /**
343
     * Get the Blade template/view key for the page.
344
     */
345
    public function getBladeView(): string
346
    {
347
        return static::$template;
348
    }
349
350
    // Section: Accessors
351
352
    /**
353
     * Get the page title to display in HTML tags like `<title>` and `<meta>` tags.
354
     */
355
    public function title(): string
356
    {
357
        return Config::getString('hyde.name', 'HydePHP').' - '.$this->title;
358
    }
359
360
    /**
361
     * Get the generated metadata for the page.
362
     */
363
    public function metadata(): PageMetadataBag
364
    {
365
        return $this->metadata;
366
    }
367
368
    /**
369
     * Can the page be shown in the navigation menu?
370
     */
371
    public function showInNavigation(): bool
372
    {
373
        return ! $this->navigation->hidden;
374
    }
375
376
    /**
377
     * Get the priority of the page in the navigation menu.
378
     */
379
    public function navigationMenuPriority(): int
380
    {
381
        return $this->navigation->priority;
382
    }
383
384
    /**
385
     * Get the label of the page in the navigation menu.
386
     */
387
    public function navigationMenuLabel(): string
388
    {
389
        return $this->navigation->label;
390
    }
391
392
    /**
393
     * Get the group of the page in the navigation menu, if any.
394
     */
395
    public function navigationMenuGroup(): ?string
396
    {
397
        return $this->navigation->group;
398
    }
399
400
    /**
401
     * Get the canonical URL for the page to use in the `<link rel="canonical">` tag.
402
     *
403
     * It can be explicitly set in the front matter using the `canonicalUrl` key,
404
     * otherwise it will be generated based on the site URL and the output path,
405
     * unless there is no configured base URL, leading to this returning null.
406
     */
407
    public function getCanonicalUrl(): ?string
408
    {
409
        /** @var ?string $value */
410
        $value = $this->matter('canonicalUrl');
411
412
        if (! empty($value)) {
413
            return $value;
414
        }
415
416
        if (Hyde::hasSiteUrl() && ! empty($this->identifier)) {
0 ignored issues
show
Bug introduced by
The method hasSiteUrl() 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

416
        if (Hyde::/** @scrutinizer ignore-call */ hasSiteUrl() && ! empty($this->identifier)) {
Loading history...
417
            return Hyde::url($this->getOutputPath());
0 ignored issues
show
Bug introduced by
The method url() 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

417
            return Hyde::/** @scrutinizer ignore-call */ url($this->getOutputPath());
Loading history...
418
        }
419
420
        return null;
421
    }
422
423
    protected function constructMetadata(): void
424
    {
425
        $this->metadata = new PageMetadataBag($this);
426
    }
427
}
428