Passed
Push — master ( 58d191...b9dd51 )
by Caen
03:23 queued 14s
created

HydePage::pathToIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

96
        return Pages::/** @scrutinizer ignore-call */ getPage(static::sourcePath($identifier));
Loading history...
97
    }
98
99
    /**
100
     * Parse a source file into a page model instance.
101
     *
102
     * @param  string  $identifier  The identifier of the page to parse.
103
     * @return static New page model instance for the parsed source file.
104
     *
105
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the file does not exist.
106
     */
107
    public static function parse(string $identifier): HydePage
108
    {
109
        return (new SourceFileParser(static::class, $identifier))->get();
110
    }
111
112
    /**
113
     * Get an array of all the source file identifiers for the model.
114
     *
115
     * Note that the values do not include the source directory or file extension.
116
     *
117
     * @return array<string>
118
     */
119
    public static function files(): array
120
    {
121
        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

121
        return Files::/** @scrutinizer ignore-call */ getFiles(static::class)->map(function (SourceFile $file): string {
Loading history...
122
            return static::pathToIdentifier($file->getPath());
123
        })->values()->toArray();
124
    }
125
126
    /**
127
     * Get a collection of all pages, parsed into page models.
128
     *
129
     * @return \Hyde\Foundation\Kernel\PageCollection<\Hyde\Pages\Concerns\HydePage>
130
     */
131
    public static function all(): PageCollection
132
    {
133
        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

133
        return Facades\Pages::/** @scrutinizer ignore-call */ getPages(static::class);
Loading history...
134
    }
135
136
    // Section: Filesystem
137
138
    /**
139
     * Get the directory in where source files are stored.
140
     */
141
    public static function sourceDirectory(): string
142
    {
143
        return static::$sourceDirectory ?? Hyde::getSourceRoot();
144
    }
145
146
    /**
147
     * Get the output subdirectory to store compiled HTML.
148
     */
149
    public static function outputDirectory(): string
150
    {
151
        return static::$outputDirectory ?? '';
152
    }
153
154
    /**
155
     * Get the file extension of the source files.
156
     */
157
    public static function fileExtension(): string
158
    {
159
        return static::$fileExtension ?? '';
160
    }
161
162
    /**
163
     * Set the output directory for the HydePage class.
164
     */
165
    public static function setSourceDirectory(string $sourceDirectory): void
166
    {
167
        static::$sourceDirectory = unslash($sourceDirectory);
168
    }
169
170
    /**
171
     * Set the source directory for the HydePage class.
172
     */
173
    public static function setOutputDirectory(string $outputDirectory): void
174
    {
175
        static::$outputDirectory = unslash($outputDirectory);
176
    }
177
178
    /**
179
     * Set the file extension for the HydePage class.
180
     */
181
    public static function setFileExtension(string $fileExtension): void
182
    {
183
        static::$fileExtension = rtrim('.'.ltrim($fileExtension, '.'), '.');
184
    }
185
186
    /**
187
     * Qualify a page identifier into a local file path for the page source file relative to the project root.
188
     */
189
    public static function sourcePath(string $identifier): string
190
    {
191
        return unslash(static::sourceDirectory().'/'.unslash($identifier).static::fileExtension());
192
    }
193
194
    /**
195
     * Qualify a page identifier into a target output file path relative to the _site output directory.
196
     */
197
    public static function outputPath(string $identifier): string
198
    {
199
        return RouteKey::fromPage(static::class, $identifier).'.html';
200
    }
201
202
    /**
203
     * Get an absolute file path to the page's source directory, or a file within it.
204
     */
205
    public static function path(string $path = ''): string
206
    {
207
        return Hyde::path(unslash(static::sourceDirectory().'/'.unslash($path)));
208
    }
209
210
    /**
211
     * Format a filename to an identifier for a given model. Unlike the basename function, any nested paths
212
     * within the source directory are retained in order to satisfy the page identifier definition.
213
     *
214
     * @param  string  $path  Example: index.blade.php
215
     * @return string Example: index
216
     */
217
    public static function pathToIdentifier(string $path): string
218
    {
219
        return unslash(Str::between(Hyde::pathToRelative($path),
220
            static::sourceDirectory().'/',
221
            static::fileExtension())
222
        );
223
    }
224
225
    /**
226
     * Get the route key base for the page model.
227
     */
228
    public static function baseRouteKey(): string
229
    {
230
        return static::outputDirectory();
231
    }
232
233
    /**
234
     * Compile the page into static HTML.
235
     *
236
     * @return string The compiled HTML for the page.
237
     */
238
    abstract public function compile(): string;
239
240
    /**
241
     * Get the path to the instance source file, relative to the project root.
242
     */
243
    public function getSourcePath(): string
244
    {
245
        return unslash(static::sourcePath($this->identifier));
246
    }
247
248
    /**
249
     * Get the path where the compiled page will be saved.
250
     *
251
     * @return string Path relative to the site output directory.
252
     */
253
    public function getOutputPath(): string
254
    {
255
        return unslash(static::outputPath($this->identifier));
256
    }
257
258
    // Section: Routing
259
260
    /**
261
     * Get the route key for the page.
262
     *
263
     * The route key is the URL path relative to the site root.
264
     *
265
     * For example, if the compiled page will be saved to _site/docs/index.html,
266
     * then this method will return 'docs/index'. Route keys are used to
267
     * identify pages, similar to how named routes work in Laravel,
268
     * only that here the name is not just arbitrary,
269
     * but also defines the output location.
270
     *
271
     * @return string The page's route key.
272
     */
273
    public function getRouteKey(): string
274
    {
275
        return $this->routeKey;
276
    }
277
278
    /**
279
     * Get the route for the page.
280
     *
281
     * @return \Hyde\Support\Models\Route The page's route.
282
     */
283
    public function getRoute(): Route
284
    {
285
        return \Hyde\Facades\Route::get($this->getRouteKey()) ?? new Route($this);
286
    }
287
288
    /**
289
     * Format the page instance to a URL path (relative to site root) with support for pretty URLs if enabled.
290
     */
291
    public function getLink(): string
292
    {
293
        return Hyde::formatLink($this->getOutputPath());
294
    }
295
296
    // Section: Getters
297
298
    /**
299
     * Get the page model's identifier property.
300
     *
301
     * The identifier is the part between the source directory and the file extension.
302
     * It may also be known as a 'slug', or previously 'basename'.
303
     *
304
     * For example, the identifier of a source file stored as '_pages/about/contact.md'
305
     * would be 'about/contact', and 'pages/about.md' would simply be 'about'.
306
     *
307
     * @return string The page's identifier.
308
     */
309
    public function getIdentifier(): string
310
    {
311
        return $this->identifier;
312
    }
313
314
    /**
315
     * Get the Blade template for the page.
316
     *
317
     * @return string Blade template/view key.
318
     */
319
    public function getBladeView(): string
320
    {
321
        return static::$template;
322
    }
323
324
    // Section: Accessors
325
326
    /**
327
     * Get the page title to display in HTML tags like <title> and <meta> tags.
328
     */
329
    public function title(): string
330
    {
331
        return config('hyde.name', 'HydePHP').' - '.$this->title;
332
    }
333
334
    public function metadata(): PageMetadataBag
335
    {
336
        return $this->metadata;
337
    }
338
339
    public function showInNavigation(): bool
340
    {
341
        return ! $this->navigation['hidden'];
342
    }
343
344
    public function navigationMenuPriority(): int
345
    {
346
        return $this->navigation['priority'];
347
    }
348
349
    public function navigationMenuLabel(): string
350
    {
351
        return $this->navigation['label'];
352
    }
353
354
    public function navigationMenuGroup(): ?string
355
    {
356
        return $this->navigation['group'];
357
    }
358
}
359