Completed
Push — internationalization ( eb25d7...2a57b9 )
by Arnaud
01:41
created

Page::getFrontmatter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/*
3
 * Copyright (c) Arnaud Ligny <[email protected]>
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace Cecil\Collection\Page;
12
13
use Cecil\Collection\Item;
14
use Cecil\Config;
15
use Cecil\Util;
16
use Cocur\Slugify\Slugify;
17
use Symfony\Component\Finder\SplFileInfo;
18
19
/**
20
 * Class Page.
21
 */
22
class Page extends Item
23
{
24
    const SLUGIFY_PATTERN = '/(^\/|[^_a-z0-9\/]|-)+/'; // should be '/^\/|[^_a-z0-9\/]+/'
25
26
    /**
27
     * @var bool
28
     */
29
    protected $virtual;
30
    /**
31
     * @var SplFileInfo
32
     */
33
    protected $file;
34
    /**
35
     * @var string
36
     */
37
    protected $type;
38
    /**
39
     * @var string
40
     */
41
    protected $folder;
42
    /**
43
     * @var string
44
     */
45
    protected $slug;
46
    /**
47
     * @var string
48
     */
49
    protected $path;
50
    /**
51
     * @var string
52
     */
53
    protected $section;
54
    /**
55
     * @var string
56
     */
57
    protected $frontmatter;
58
    /**
59
     * @var string
60
     */
61
    protected $body;
62
    /**
63
     * @var string
64
     */
65
    protected $html;
66
    /**
67
     * @var Slugify
68
     */
69
    private static $slugifier;
70
    /**
71
     * @var string
72
     */
73
    protected $language;
74
75
    /**
76
     * Constructor.
77
     *
78
     * @param string $id
79
     */
80
    public function __construct(string $id)
81
    {
82
        parent::__construct($id);
83
        $this->setVirtual(true);
84
        $this->setType(Type::PAGE);
85
        // default variables
86
        $this->setVariables([
87
            'title'            => 'Page Title',
88
            'date'             => new \DateTime(),
89
            'updated'          => new \DateTime(),
90
            'weight'           => null,
91
            'filepath'         => null,
92
            'published'        => true,
93
            'content_template' => 'page.content.twig',
94
        ]);
95
    }
96
97
    /**
98
     * Turn a path (string) into a slug (URI).
99
     *
100
     * @param string $path
101
     *
102
     * @return string
103
     */
104
    public static function slugify(string $path): string
105
    {
106
        if (!self::$slugifier instanceof Slugify) {
107
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
108
        }
109
110
        return self::$slugifier->slugify($path);
111
    }
112
113
    /**
114
     * Create ID from file.
115
     *
116
     * @param SplFileInfo $file
117
     *
118
     * @return string
119
     */
120
    public static function createId(SplFileInfo $file): string
121
    {
122
        $relpath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
123
        $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension())));
124
125
        // kill me with your fucking index!
126
        if ($relpath && $basename == 'index') {
127
            return $relpath;
128
        }
129
130
        return trim($relpath.'/'.$basename, '/');
131
    }
132
133
    /**
134
     * Set file.
135
     *
136
     * @param SplFileInfo $file
137
     *
138
     * @return self
139
     */
140
    public function setFile(SplFileInfo $file): self
141
    {
142
        $this->setVirtual(false);
143
        $this->file = $file;
144
145
        /*
146
         * File path components
147
         */
148
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
149
        $fileExtension = $this->file->getExtension();
150
        $fileName = $this->file->getBasename('.'.$fileExtension);
151
        /*
152
         * Set protected variables
153
         */
154
        $this->setFolder($fileRelativePath); // ie: "blog"
155
        $this->setSlug($fileName); // ie: "post-1"
156
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
157
        /*
158
         * Set default variables
159
         */
160
        $this->setVariables([
161
            'title'    => PrefixSuffix::sub($fileName),
162
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
163
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
164
            'filepath' => $this->file->getRelativePathname(),
165
        ]);
166
        /*
167
         * Set specific variables
168
         */
169
        // file has a prefix
170
        if (PrefixSuffix::hasPrefix($fileName)) {
171
            $prefix = PrefixSuffix::getPrefix($fileName);
172
            // prefix is a valid date?
173
            if (Util::dateIsValid($prefix)) {
174
                $this->setVariable('date', (string) $prefix);
175
            } else {
176
                // prefix is an integer: used for sorting
177
                $this->setVariable('weight', (int) $prefix);
178
            }
179
        }
180
        // file has a suffix
181
        if (PrefixSuffix::hasSuffix($fileName)) {
182
            $this->language = PrefixSuffix::getSuffix($fileName);
183
            $this->setVariable('language', $this->language);
184
            //$this->setPath($this->language.(null !== $this->getFolder() ? '/'.$this->getFolder() : '').'/'.$this->getSlug());
185
        }
186
        $this->setVariable('langref', PrefixSuffix::sub($fileName));
187
188
        return $this;
189
    }
190
191
    /**
192
     * Parse file content.
193
     *
194
     * @return self
195
     */
196
    public function parse(): self
197
    {
198
        $parser = new Parser($this->file);
199
        $parsed = $parser->parse();
200
        $this->frontmatter = $parsed->getFrontmatter();
201
        $this->body = $parsed->getBody();
202
203
        return $this;
204
    }
205
206
    /**
207
     * Get frontmatter.
208
     *
209
     * @return string|null
210
     */
211
    public function getFrontmatter(): ?string
212
    {
213
        return $this->frontmatter;
214
    }
215
216
    /**
217
     * Get body as raw.
218
     *
219
     * @return string
220
     */
221
    public function getBody(): ?string
222
    {
223
        return $this->body;
224
    }
225
226
    /**
227
     * Set virtual status.
228
     *
229
     * @param bool $virtual
230
     *
231
     * @return self
232
     */
233
    public function setVirtual(bool $virtual): self
234
    {
235
        $this->virtual = $virtual;
236
237
        return $this;
238
    }
239
240
    /**
241
     * Is current page is virtual?
242
     *
243
     * @return bool
244
     */
245
    public function isVirtual(): bool
246
    {
247
        return $this->virtual;
248
    }
249
250
    /**
251
     * Set page type.
252
     *
253
     * @param string $type
254
     *
255
     * @return self
256
     */
257
    public function setType(string $type): self
258
    {
259
        $this->type = new Type($type);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Cecil\Collection\Page\Type($type) of type object<Cecil\Collection\Page\Type> is incompatible with the declared type string of property $type.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
260
261
        return $this;
262
    }
263
264
    /**
265
     * Get page type.
266
     *
267
     * @return string
268
     */
269
    public function getType(): string
270
    {
271
        return (string) $this->type;
272
    }
273
274
    /**
275
     * Set path without slug.
276
     *
277
     * @param string $folder
278
     *
279
     * @return self
280
     */
281
    public function setFolder(string $folder): self
282
    {
283
        $this->folder = self::slugify($folder);
284
285
        return $this;
286
    }
287
288
    /**
289
     * Get path without slug.
290
     *
291
     * @return string|null
292
     */
293
    public function getFolder(): ?string
294
    {
295
        return $this->folder;
296
    }
297
298
    /**
299
     * Set slug.
300
     *
301
     * @param string $slug
302
     *
303
     * @return self
304
     */
305
    public function setSlug(string $slug): self
306
    {
307
        if (!$this->slug) {
308
            $slug = self::slugify(PrefixSuffix::sub($slug));
309
        }
310
        // force slug and update path
311
        if ($this->slug && $this->slug != $slug) {
312
            $this->setPath($this->getFolder().'/'.$slug);
313
        }
314
        $this->slug = $slug;
315
316
        return $this;
317
    }
318
319
    /**
320
     * Get slug.
321
     *
322
     * @return string
323
     */
324
    public function getSlug(): string
325
    {
326
        return $this->slug;
327
    }
328
329
    /**
330
     * Set path.
331
     *
332
     * @param string $path
333
     *
334
     * @return self
335
     */
336
    public function setPath(string $path): self
337
    {
338
        $path = self::slugify(PrefixSuffix::sub($path));
339
        // special case: homepage
340
        if ($path == 'index') {
341
            $this->path = '';
342
343
            return $this;
344
        }
345
        // special case: custom section index (ie: content/section/index.md)
346
        if (substr($path, -6) == '/index') {
347
            $path = substr($path, 0, strlen($path) - 6);
348
        }
349
        $this->path = $path;
350
        // explode path by slash
351
        $lastslash = strrpos($this->path, '/');
352
        if ($lastslash === false) {
353
            $this->section = null;
354
            $this->folder = null;
355
            $this->slug = $this->path;
356
        } else {
357
            if (!$this->virtual) {
358
                $this->section = explode('/', $this->path)[0];
359
            }
360
            $this->folder = substr($this->path, 0, $lastslash);
361
            $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));
362
        }
363
364
        return $this;
365
    }
366
367
    /**
368
     * Get path.
369
     *
370
     * @return string|null
371
     */
372
    public function getPath(): ?string
373
    {
374
        return $this->path;
375
    }
376
377
    /**
378
     * @see getPath()
379
     *
380
     * @return string|null
381
     */
382
    public function getPathname(): ?string
383
    {
384
        return $this->getPath();
385
    }
386
387
    /**
388
     * Set section.
389
     *
390
     * @param string $section
391
     *
392
     * @return self
393
     */
394
    public function setSection(string $section): self
395
    {
396
        $this->section = $section;
397
398
        return $this;
399
    }
400
401
    /**
402
     * Get section.
403
     *
404
     * @return string|null
405
     */
406
    public function getSection(): ?string
407
    {
408
        return $this->section;
409
    }
410
411
    /**
412
     * Set body as HTML.
413
     *
414
     * @param string $html
415
     *
416
     * @return self
417
     */
418
    public function setBodyHtml(string $html): self
419
    {
420
        $this->html = $html;
421
422
        return $this;
423
    }
424
425
    /**
426
     * Get body as HTML.
427
     *
428
     * @return string|null
429
     */
430
    public function getBodyHtml(): ?string
431
    {
432
        return $this->html;
433
    }
434
435
    /**
436
     * @see getBodyHtml()
437
     *
438
     * @return string|null
439
     */
440
    public function getContent(): ?string
441
    {
442
        return $this->getBodyHtml();
443
    }
444
445
    /**
446
     * Return output file.
447
     *
448
     * Use cases:
449
     *   - default: path + suffix + extension (ie: blog/post-1/index.html)
450
     *   - subpath: path + subpath + suffix + extension (ie: blog/post-1/amp/index.html)
451
     *   - ugly: path + extension (ie: 404.html, sitemap.xml, robots.txt)
452
     *   - path only (ie: _redirects)
453
     *
454
     * @param string $format
455
     * @param Config $config
0 ignored issues
show
Documentation introduced by
Should the type for parameter $config not be null|Config?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
456
     *
457
     * @return string
458
     */
459
    public function getOutputFile(string $format, Config $config = null): string
460
    {
461
        $path = $this->getPath();
462
        $subpath = '';
463
        $suffix = '/index';
464
        $extension = 'html';
465
        $uglyurl = $this->getVariable('uglyurl') ? true : false;
466
        $language = '';
467
468
        // site config
469
        if ($config) {
470
            $subpath = $config->get(sprintf('output.formats.%s.subpath', $format));
471
            $suffix = $config->get(sprintf('output.formats.%s.suffix', $format));
472
            $extension = $config->get(sprintf('output.formats.%s.extension', $format));
473
        }
474
        // if ugly URL: not suffix
475
        if ($uglyurl) {
476
            $suffix = '';
477
        }
478
        // format strings
479
        if ($subpath) {
480
            $subpath = sprintf('/%s', ltrim($subpath, '/'));
481
        }
482
        if ($suffix) {
483
            $suffix = sprintf('/%s', ltrim($suffix, '/'));
484
        }
485
        if ($extension) {
486
            $extension = sprintf('.%s', $extension);
487
        }
488
        // special case: homepage ('index' from hell!)
489
        if (!$path && !$suffix) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $path of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
490
            $path = 'index';
491
        }
492
        // language
493
        if (!empty($this->language)) {
494
            $language = $this->language.'/';
495
        }
496
497
        return $language.$path.$subpath.$suffix.$extension;
498
    }
499
500
    /**
501
     * Return URL.
502
     *
503
     * @param string $format
504
     * @param Config $config
0 ignored issues
show
Documentation introduced by
Should the type for parameter $config not be null|Config?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
505
     *
506
     * @return string
507
     */
508
    public function getUrl(string $format = 'html', Config $config = null): string
509
    {
510
        $uglyurl = $this->getVariable('uglyurl') ? true : false;
511
        $output = $this->getOutputFile($format, $config);
512
513
        if (!$uglyurl) {
514
            $output = str_replace('index.html', '', $output);
515
        }
516
517
        return $output;
518
    }
519
520
    /*
521
     * Helper to set and get variables.
522
     */
523
524
    /**
525
     * Set an array as variables.
526
     *
527
     * @param array $variables
528
     *
529
     * @throws \Exception
530
     *
531
     * @return $this
532
     */
533
    public function setVariables($variables)
534
    {
535
        if (!is_array($variables)) {
536
            throw new \Exception(sprintf(
537
                'Can\'t set variables in "%s": array expected, not %s',
538
                $this->getId(),
539
                gettype($variables)
540
            ));
541
        }
542
        foreach ($variables as $key => $value) {
543
            $this->setVariable($key, $value);
544
        }
545
546
        return $this;
547
    }
548
549
    /**
550
     * Get all variables.
551
     *
552
     * @return array
553
     */
554
    public function getVariables(): array
555
    {
556
        return $this->properties;
557
    }
558
559
    /**
560
     * Set a variable.
561
     *
562
     * @param $name
563
     * @param $value
564
     *
565
     * @throws \Exception
566
     *
567
     * @return $this
568
     */
569
    public function setVariable($name, $value)
570
    {
571
        if (is_bool($value)) {
572
            $value = $value ?: 0;
573
        }
574
        switch ($name) {
575
            case 'date':
576
                try {
577
                    $date = Util::dateToDatetime($value);
578
                } catch (\Exception $e) {
579
                    throw new \Exception(sprintf(
580
                        'Expected date format (ie: "2012-10-08") for "date" in "%s" instead of "%s"',
581
                        $this->getId(),
582
                        $value
583
                    ));
584
                }
585
                $this->offsetSet('date', $date);
586
                break;
587
            case 'draft':
588
                if ($value === true) {
589
                    $this->offsetSet('published', false);
590
                }
591
                break;
592
            case 'path':
593
            case 'slug':
594
                $slugify = self::slugify($value);
595
                if ($value != $slugify) {
596
                    throw new \Exception(sprintf(
597
                        '"%s" variable should be "%s" (not "%s") in "%s"',
598
                        $name,
599
                        $slugify,
600
                        $value,
601
                        $this->getId()
602
                    ));
603
                }
604
                // @see setPath()
605
                // @see setSlug()
606
                $method = 'set'.\ucfirst($name);
607
                $this->$method($value);
608
                break;
609
            default:
610
                $this->offsetSet($name, $value);
611
        }
612
613
        return $this;
614
    }
615
616
    /**
617
     * Is variable exist?
618
     *
619
     * @param string $name
620
     *
621
     * @return bool
622
     */
623
    public function hasVariable(string $name): bool
624
    {
625
        return $this->offsetExists($name);
626
    }
627
628
    /**
629
     * Get a variable.
630
     *
631
     * @param string $name
632
     *
633
     * @return mixed|null
634
     */
635
    public function getVariable(string $name)
636
    {
637
        if ($this->offsetExists($name)) {
638
            return $this->offsetGet($name);
639
        }
640
    }
641
642
    /**
643
     * Unset a variable.
644
     *
645
     * @param string $name
646
     *
647
     * @return $this
648
     */
649
    public function unVariable(string $name): self
650
    {
651
        if ($this->offsetExists($name)) {
652
            $this->offsetUnset($name);
653
        }
654
655
        return $this;
656
    }
657
}
658