Passed
Push — fix/log ( 2ed6d5 )
by Arnaud
03:09
created

Page::getFilePath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the Cecil/Cecil package.
4
 *
5
 * Copyright (c) Arnaud Ligny <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Cecil\Collection\Page;
14
15
use Cecil\Collection\Item;
16
use Cecil\Util;
17
use Cocur\Slugify\Slugify;
18
use Symfony\Component\Finder\SplFileInfo;
19
20
/**
21
 * Class Page.
22
 */
23
class Page extends Item
24
{
25
    const SLUGIFY_PATTERN = '/(^\/|[^._a-z0-9\/]|-)+/'; // should be '/^\/|[^_a-z0-9\/]+/'
26
27
    /** @var bool True if page is not created from a Markdown file. */
28
    protected $virtual;
29
30
    /** @var SplFileInfo */
31
    protected $file;
32
33
    /** @var string Homepage, Page, Section, etc. */
34
    protected $type;
35
36
    /** @var string */
37
    protected $folder;
38
39
    /** @var string */
40
    protected $slug;
41
42
    /** @var string folder + slug. */
43
    protected $path;
44
45
    /** @var string */
46
    protected $section;
47
48
    /** @var string */
49
    protected $frontmatter;
50
51
    /** @var string Body before conversion. */
52
    protected $body;
53
54
    /** @var array Front matter before conversion. */
55
    protected $fmVariables = [];
56
57
    /** @var string Body after Markdown conversion. */
58
    protected $html;
59
60
    /** @var Slugify */
61
    private static $slugifier;
62
63
    public function __construct(string $id)
64
    {
65
        parent::__construct($id);
66
        $this->setVirtual(true);
67
        $this->setType(Type::PAGE);
68
        // default variables
69
        $this->setVariables([
70
            'title'            => 'Page Title',
71
            'date'             => new \DateTime(),
72
            'updated'          => new \DateTime(),
73
            'weight'           => null,
74
            'filepath'         => null,
75
            'published'        => true,
76
            'content_template' => 'page.content.twig',
77
        ]);
78
    }
79
80
    /**
81
     * Turns a path (string) into a slug (URI).
82
     */
83
    public static function slugify(string $path): string
84
    {
85
        if (!self::$slugifier instanceof Slugify) {
86
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
87
        }
88
89
        return self::$slugifier->slugify($path);
90
    }
91
92
    /**
93
     * Creates the ID from the file path.
94
     */
95
    public static function createId(SplFileInfo $file): string
96
    {
97
        $relativepath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
98
        $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension())));
99
        // case of "README" -> index
100
        $basename = (string) str_ireplace('readme', 'index', $basename);
101
        // case of section's index: "section/index" -> "section"
102
        if (!empty($relativepath) && PrefixSuffix::sub($basename) == 'index') {
103
            // case of a localized section
104
            if (PrefixSuffix::hasSuffix($basename)) {
105
                return $relativepath.'.'.PrefixSuffix::getSuffix($basename);
106
            }
107
108
            return $relativepath;
109
        }
110
111
        return trim(Util::joinPath($relativepath, $basename), '/');
112
    }
113
114
    /**
115
     * Returns the ID of a page without language suffix.
116
     */
117
    public function getIdWithoutLang(): string
118
    {
119
        return PrefixSuffix::sub($this->getId());
120
    }
121
122
    /**
123
     * Set file.
124
     */
125
    public function setFile(SplFileInfo $file): self
126
    {
127
        $this->setVirtual(false);
128
        $this->file = $file;
129
130
        /*
131
         * File path components
132
         */
133
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
134
        $fileExtension = $this->file->getExtension();
135
        $fileName = $this->file->getBasename('.'.$fileExtension);
136
        // case of "README" -> "index"
137
        $fileName = (string) str_ireplace('readme', 'index', $fileName);
138
        // case of "index" = home page
139
        if (empty($this->file->getRelativePath()) && PrefixSuffix::sub($fileName) == 'index') {
140
            $this->setType(Type::HOMEPAGE);
141
        }
142
        /*
143
         * Set protected variables
144
         */
145
        $this->setFolder($fileRelativePath); // ie: "blog"
146
        $this->setSlug($fileName); // ie: "post-1"
147
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
148
        /*
149
         * Set default variables
150
         */
151
        $this->setVariables([
152
            'title'    => PrefixSuffix::sub($fileName),
153
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
154
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
155
            'filepath' => $this->file->getRelativePathname(),
156
        ]);
157
        /*
158
         * Set specific variables
159
         */
160
        // is file has a prefix?
161
        if (PrefixSuffix::hasPrefix($fileName)) {
162
            $prefix = PrefixSuffix::getPrefix($fileName);
163
            if ($prefix !== null) {
164
                // prefix is a valid date?
165
                if (Util\Date::isDateValid($prefix)) {
166
                    $this->setVariable('date', (string) $prefix);
167
                } else {
168
                    // prefix is an integer: used for sorting
169
                    $this->setVariable('weight', (int) $prefix);
170
                }
171
            }
172
        }
173
        // is file has a language suffix?
174
        if (PrefixSuffix::hasSuffix($fileName)) {
175
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
176
        }
177
        // set reference between translations
178
        $this->setVariable('langref', $this->getPath());
179
180
        return $this;
181
    }
182
183
    /**
184
     * Returns file real path.
185
     */
186
    public function getFilePath(): ?string
187
    {
188
        if ($this->file instanceof SplFileInfo) {
0 ignored issues
show
introduced by
$this->file is always a sub-type of Symfony\Component\Finder\SplFileInfo.
Loading history...
189
            return $this->file->getRealPath();
190
        }
191
192
        return null;
193
    }
194
195
    /**
196
     * Parse file content.
197
     */
198
    public function parse(): self
199
    {
200
        $parser = new Parser($this->file);
201
        $parsed = $parser->parse();
202
        $this->frontmatter = $parsed->getFrontmatter();
203
        $this->body = $parsed->getBody();
204
205
        return $this;
206
    }
207
208
    /**
209
     * Get frontmatter.
210
     */
211
    public function getFrontmatter(): ?string
212
    {
213
        return $this->frontmatter;
214
    }
215
216
    /**
217
     * Get body as raw.
218
     */
219
    public function getBody(): ?string
220
    {
221
        return $this->body;
222
    }
223
224
    /**
225
     * Set virtual status.
226
     */
227
    public function setVirtual(bool $virtual): self
228
    {
229
        $this->virtual = $virtual;
230
231
        return $this;
232
    }
233
234
    /**
235
     * Is current page is virtual?
236
     */
237
    public function isVirtual(): bool
238
    {
239
        return $this->virtual;
240
    }
241
242
    /**
243
     * Set page type.
244
     */
245
    public function setType(string $type): self
246
    {
247
        $this->type = new Type($type);
248
249
        return $this;
250
    }
251
252
    /**
253
     * Get page type.
254
     */
255
    public function getType(): string
256
    {
257
        return (string) $this->type;
258
    }
259
260
    /**
261
     * Set path without slug.
262
     */
263
    public function setFolder(string $folder): self
264
    {
265
        $this->folder = self::slugify($folder);
266
267
        return $this;
268
    }
269
270
    /**
271
     * Get path without slug.
272
     */
273
    public function getFolder(): ?string
274
    {
275
        return $this->folder;
276
    }
277
278
    /**
279
     * Set slug.
280
     */
281
    public function setSlug(string $slug): self
282
    {
283
        if (!$this->slug) {
284
            $slug = self::slugify(PrefixSuffix::sub($slug));
285
        }
286
        // force slug and update path
287
        if ($this->slug && $this->slug != $slug) {
288
            $this->setPath($this->getFolder().'/'.$slug);
289
        }
290
        $this->slug = $slug;
291
292
        return $this;
293
    }
294
295
    /**
296
     * Get slug.
297
     */
298
    public function getSlug(): string
299
    {
300
        return $this->slug;
301
    }
302
303
    /**
304
     * Set path.
305
     */
306
    public function setPath(string $path): self
307
    {
308
        $path = self::slugify(PrefixSuffix::sub($path));
309
310
        // case of homepage
311
        if ($path == 'index') {
312
            $this->path = '';
313
314
            return $this;
315
        }
316
317
        // case of custom sections' index (ie: content/section/index.md)
318
        if (substr($path, -6) == '/index') {
319
            $path = substr($path, 0, strlen($path) - 6);
320
        }
321
        $this->path = $path;
322
323
        // case of root pages
324
        $lastslash = strrpos($this->path, '/');
325
        if ($lastslash === false) {
326
            $this->slug = $this->path;
327
328
            return $this;
329
        }
330
331
        if (!$this->virtual && $this->getSection() === null) {
332
            $this->section = explode('/', $this->path)[0];
333
        }
334
        $this->folder = substr($this->path, 0, $lastslash);
335
        $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));
336
337
        return $this;
338
    }
339
340
    /**
341
     * Get path.
342
     */
343
    public function getPath(): ?string
344
    {
345
        return $this->path;
346
    }
347
348
    /**
349
     * @see getPath()
350
     */
351
    public function getPathname(): ?string
352
    {
353
        return $this->getPath();
354
    }
355
356
    /**
357
     * Set section.
358
     */
359
    public function setSection(string $section): self
360
    {
361
        $this->section = $section;
362
363
        return $this;
364
    }
365
366
    /**
367
     * Get section.
368
     */
369
    public function getSection(): ?string
370
    {
371
        return !empty($this->section) ? $this->section : null;
372
    }
373
374
    /**
375
     * Set body as HTML.
376
     */
377
    public function setBodyHtml(string $html): self
378
    {
379
        $this->html = $html;
380
381
        return $this;
382
    }
383
384
    /**
385
     * Get body as HTML.
386
     */
387
    public function getBodyHtml(): ?string
388
    {
389
        return $this->html;
390
    }
391
392
    /**
393
     * @see getBodyHtml()
394
     */
395
    public function getContent(): ?string
396
    {
397
        return $this->getBodyHtml();
398
    }
399
400
    /*
401
     * Helpers to set and get variables.
402
     */
403
404
    /**
405
     * Set an array as variables.
406
     *
407
     * @throws \Exception
408
     */
409
    public function setVariables(array $variables): self
410
    {
411
        foreach ($variables as $key => $value) {
412
            $this->setVariable($key, $value);
413
        }
414
415
        return $this;
416
    }
417
418
    /**
419
     * Get all variables.
420
     */
421
    public function getVariables(): array
422
    {
423
        return $this->properties;
424
    }
425
426
    /**
427
     * Set a variable.
428
     *
429
     * @param string $name
430
     * @param mixed  $value
431
     *
432
     * @throws \Exception
433
     */
434
    public function setVariable(string $name, $value): self
435
    {
436
        if (is_bool($value)) {
437
            $value = $value ?: 0;
438
        }
439
        switch ($name) {
440
            case 'date':
441
                try {
442
                    $date = Util\Date::dateToDatetime($value);
443
                } catch (\Exception $e) {
444
                    throw new \Exception(sprintf(
445
                        'Expected date format (ie: "2012-10-08") for "date" in "%s" instead of "%s"',
446
                        $this->getId(),
447
                        (string) $value
448
                    ));
449
                }
450
                $this->offsetSet('date', $date);
451
                break;
452
            case 'draft':
453
                if ($value === true) {
454
                    $this->offsetSet('published', false);
455
                }
456
                break;
457
            case 'path':
458
            case 'slug':
459
                $slugify = self::slugify((string) $value);
460
                if ($value != $slugify) {
461
                    throw new \Exception(sprintf(
462
                        '"%s" variable should be "%s" (not "%s") in "%s"',
463
                        $name,
464
                        $slugify,
465
                        (string) $value,
466
                        $this->getId()
467
                    ));
468
                }
469
                /** @see setPath() */
470
                /** @see setSlug() */
471
                $method = 'set'.\ucfirst($name);
472
                $this->$method($value);
473
                break;
474
            default:
475
                $this->offsetSet($name, $value);
476
        }
477
478
        return $this;
479
    }
480
481
    /**
482
     * Is variable exists?
483
     */
484
    public function hasVariable(string $name): bool
485
    {
486
        return $this->offsetExists($name);
487
    }
488
489
    /**
490
     * Get a variable.
491
     *
492
     * @return mixed|null
493
     */
494
    public function getVariable(string $name)
495
    {
496
        if ($this->offsetExists($name)) {
497
            return $this->offsetGet($name);
498
        }
499
    }
500
501
    /**
502
     * Unset a variable.
503
     */
504
    public function unVariable(string $name): self
505
    {
506
        if ($this->offsetExists($name)) {
507
            $this->offsetUnset($name);
508
        }
509
510
        return $this;
511
    }
512
513
    /**
514
     * Set front matter (only) variables.
515
     */
516
    public function setFmVariables(array $variables): self
517
    {
518
        $this->fmVariables = $variables;
519
520
        return $this;
521
    }
522
523
    /**
524
     * Get front matter variables.
525
     */
526
    public function getFmVariables(): array
527
    {
528
        return $this->fmVariables;
529
    }
530
}
531