Completed
Push — Assets/Image ( 7a290d...cdd250 )
by Arnaud
16:11 queued 12:51
created

Page::setSection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 1
b 0
f 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
    /**
72
     * Constructor.
73
     *
74
     * @param string $id
75
     */
76
    public function __construct(string $id)
77
    {
78
        parent::__construct($id);
79
        $this->setVirtual(true);
80
        $this->setType(Type::PAGE);
81
        // default variables
82
        $this->setVariables([
83
            'title'            => 'Page Title',
84
            'date'             => new \DateTime(),
85
            'updated'          => new \DateTime(),
86
            'weight'           => null,
87
            'filepath'         => null,
88
            'published'        => true,
89
            'content_template' => 'page.content.twig',
90
        ]);
91
    }
92
93
    /**
94
     * Turn a path (string) into a slug (URI).
95
     *
96
     * @param string $path
97
     *
98
     * @return string
99
     */
100
    public static function slugify(string $path): string
101
    {
102
        if (!self::$slugifier instanceof Slugify) {
103
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
104
        }
105
106
        return self::$slugifier->slugify($path);
107
    }
108
109
    /**
110
     * Create ID from file.
111
     *
112
     * @param SplFileInfo $file
113
     *
114
     * @return string
115
     */
116
    public static function createId(SplFileInfo $file): string
117
    {
118
        $relpath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
119
        $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension())));
120
121
        // kill me with your fucking index!
122
        if ($relpath && $basename == 'index') {
123
            return $relpath;
124
        }
125
126
        return trim($relpath.'/'.$basename, '/');
127
    }
128
129
    /**
130
     * Set file.
131
     *
132
     * @param SplFileInfo $file
133
     *
134
     * @return self
135
     */
136
    public function setFile(SplFileInfo $file): self
137
    {
138
        $this->setVirtual(false);
139
        $this->file = $file;
140
141
        /*
142
         * File path components
143
         */
144
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
145
        $fileExtension = $this->file->getExtension();
146
        $fileName = $this->file->getBasename('.'.$fileExtension);
147
        /*
148
         * Set protected variables
149
         */
150
        $this->setFolder($fileRelativePath); // ie: "blog"
151
        $this->setSlug($fileName); // ie: "post-1"
152
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
153
        /*
154
         * Set default variables
155
         */
156
        $this->setVariables([
157
            'title'    => PrefixSuffix::sub($fileName),
158
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
159
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
160
            'filepath' => $this->file->getRelativePathname(),
161
        ]);
162
        /*
163
         * Set specific variables
164
         */
165
        // file has a prefix
166
        if (PrefixSuffix::hasPrefix($fileName)) {
167
            $prefix = PrefixSuffix::getPrefix($fileName);
168
            // prefix is a valid date?
169
            if (Util::isDateValid($prefix)) {
170
                $this->setVariable('date', (string) $prefix);
171
            } else {
172
                // prefix is an integer: used for sorting
173
                $this->setVariable('weight', (int) $prefix);
174
            }
175
        }
176
        // file has a suffix
177
        if (PrefixSuffix::hasSuffix($fileName)) {
178
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
179
        }
180
        $this->setVariable('langref', PrefixSuffix::sub($fileName));
181
182
        return $this;
183
    }
184
185
    /**
186
     * Parse file content.
187
     *
188
     * @return self
189
     */
190
    public function parse(): self
191
    {
192
        $parser = new Parser($this->file);
193
        $parsed = $parser->parse();
194
        $this->frontmatter = $parsed->getFrontmatter();
195
        $this->body = $parsed->getBody();
196
197
        return $this;
198
    }
199
200
    /**
201
     * Get frontmatter.
202
     *
203
     * @return string|null
204
     */
205
    public function getFrontmatter(): ?string
206
    {
207
        return $this->frontmatter;
208
    }
209
210
    /**
211
     * Get body as raw.
212
     *
213
     * @return string
214
     */
215
    public function getBody(): ?string
216
    {
217
        return $this->body;
218
    }
219
220
    /**
221
     * Set virtual status.
222
     *
223
     * @param bool $virtual
224
     *
225
     * @return self
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
     * @return bool
238
     */
239
    public function isVirtual(): bool
240
    {
241
        return $this->virtual;
242
    }
243
244
    /**
245
     * Set page type.
246
     *
247
     * @param string $type
248
     *
249
     * @return self
250
     */
251
    public function setType(string $type): self
252
    {
253
        $this->type = new Type($type);
254
255
        return $this;
256
    }
257
258
    /**
259
     * Get page type.
260
     *
261
     * @return string
262
     */
263
    public function getType(): string
264
    {
265
        return (string) $this->type;
266
    }
267
268
    /**
269
     * Set path without slug.
270
     *
271
     * @param string $folder
272
     *
273
     * @return self
274
     */
275
    public function setFolder(string $folder): self
276
    {
277
        $this->folder = self::slugify($folder);
278
279
        return $this;
280
    }
281
282
    /**
283
     * Get path without slug.
284
     *
285
     * @return string|null
286
     */
287
    public function getFolder(): ?string
288
    {
289
        return $this->folder;
290
    }
291
292
    /**
293
     * Set slug.
294
     *
295
     * @param string $slug
296
     *
297
     * @return self
298
     */
299
    public function setSlug(string $slug): self
300
    {
301
        if (!$this->slug) {
302
            $slug = self::slugify(PrefixSuffix::sub($slug));
303
        }
304
        // force slug and update path
305
        if ($this->slug && $this->slug != $slug) {
306
            $this->setPath($this->getFolder().'/'.$slug);
307
        }
308
        $this->slug = $slug;
309
310
        return $this;
311
    }
312
313
    /**
314
     * Get slug.
315
     *
316
     * @return string
317
     */
318
    public function getSlug(): string
319
    {
320
        return $this->slug;
321
    }
322
323
    /**
324
     * Set path.
325
     *
326
     * @param string $path
327
     *
328
     * @return self
329
     */
330
    public function setPath(string $path): self
331
    {
332
        $path = self::slugify(PrefixSuffix::sub($path));
333
        // special case: homepage
334
        if ($path == 'index') {
335
            $this->path = '';
336
337
            return $this;
338
        }
339
        // special case: custom section index (ie: content/section/index.md)
340
        if (substr($path, -6) == '/index') {
341
            $path = substr($path, 0, strlen($path) - 6);
342
        }
343
        $this->path = $path;
344
        // explode path by slash
345
        $lastslash = strrpos($this->path, '/');
346
        if ($lastslash === false) {
347
            $this->section = null;
348
            $this->folder = null;
349
            $this->slug = $this->path;
350
        } else {
351
            if (!$this->virtual) {
352
                $this->section = explode('/', $this->path)[0];
353
            }
354
            $this->folder = substr($this->path, 0, $lastslash);
355
            $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));
356
        }
357
358
        return $this;
359
    }
360
361
    /**
362
     * Get path.
363
     *
364
     * @return string|null
365
     */
366
    public function getPath(): ?string
367
    {
368
        return $this->path;
369
    }
370
371
    /**
372
     * @see getPath()
373
     *
374
     * @return string|null
375
     */
376
    public function getPathname(): ?string
377
    {
378
        return $this->getPath();
379
    }
380
381
    /**
382
     * Set section.
383
     *
384
     * @param string $section
385
     *
386
     * @return self
387
     */
388
    public function setSection(string $section): self
389
    {
390
        $this->section = $section;
391
392
        return $this;
393
    }
394
395
    /**
396
     * Get section.
397
     *
398
     * @return string|null
399
     */
400
    public function getSection(): ?string
401
    {
402
        return $this->section;
403
    }
404
405
    /**
406
     * Set body as HTML.
407
     *
408
     * @param string $html
409
     *
410
     * @return self
411
     */
412
    public function setBodyHtml(string $html): self
413
    {
414
        $this->html = $html;
415
416
        return $this;
417
    }
418
419
    /**
420
     * Get body as HTML.
421
     *
422
     * @return string|null
423
     */
424
    public function getBodyHtml(): ?string
425
    {
426
        return $this->html;
427
    }
428
429
    /**
430
     * @see getBodyHtml()
431
     *
432
     * @return string|null
433
     */
434
    public function getContent(): ?string
435
    {
436
        return $this->getBodyHtml();
437
    }
438
439
    /**
440
     * Return output file.
441
     *
442
     * Use cases:
443
     *   - default: path + suffix + extension (ie: blog/post-1/index.html)
444
     *   - subpath: path + subpath + suffix + extension (ie: blog/post-1/amp/index.html)
445
     *   - ugly: path + extension (ie: 404.html, sitemap.xml, robots.txt)
446
     *   - path only (ie: _redirects)
447
     *   - i18n: language + path + suffix + extension (ie: fr/blog/page/index.html)
448
     *
449
     * @param string      $format
450
     * @param null|Config $config
451
     *
452
     * @return string
453
     */
454
    public function getOutputFile(string $format, Config $config = null): string
455
    {
456
        $path = $this->getPath();
457
        $subpath = '';
458
        $suffix = '/index';
459
        $extension = 'html';
460
        $uglyurl = (bool) $this->getVariable('uglyurl');
461
        $language = $this->getVariable('language');
462
463
        // site config
464
        if ($config) {
465
            $subpath = (string) $config->getOutputFormatProperty($format, 'subpath');
466
            $suffix = (string) $config->getOutputFormatProperty($format, 'suffix');
467
            $extension = (string) $config->getOutputFormatProperty($format, 'extension');
468
        }
469
470
        // if ugly URL: not suffix
471
        if ($uglyurl) {
472
            $suffix = null;
473
        }
474
        // format strings
475
        if ($subpath) {
476
            $subpath = \sprintf('/%s', ltrim($subpath, '/'));
477
        }
478
        if ($suffix) {
479
            $suffix = \sprintf('/%s', ltrim($suffix, '/'));
480
        }
481
        if ($extension) {
482
            $extension = \sprintf('.%s', $extension);
483
        }
484
        if ($language !== null) {
485
            $language = \sprintf('%s/', $language);
486
        }
487
        // special case: homepage ('index' from hell!)
488
        if (empty($path) && $suffix === null) {
489
            $path = 'index';
490
        }
491
492
        return $language.$path.$subpath.$suffix.$extension;
493
    }
494
495
    /**
496
     * Return URL.
497
     *
498
     * @param string      $format
499
     * @param null|Config $config
500
     *
501
     * @return string
502
     */
503
    public function getUrl(string $format = 'html', Config $config = null): string
504
    {
505
        $uglyurl = $this->getVariable('uglyurl') ? true : false;
506
        $output = $this->getOutputFile($format, $config);
507
508
        if (!$uglyurl) {
509
            $output = str_replace('index.html', '', $output);
510
        }
511
512
        return $output;
513
    }
514
515
    /*
516
     * Helper to set and get variables.
517
     */
518
519
    /**
520
     * Set an array as variables.
521
     *
522
     * @param array $variables
523
     *
524
     * @throws \Exception
525
     *
526
     * @return $this
527
     */
528
    public function setVariables(array $variables)
529
    {
530
        foreach ($variables as $key => $value) {
531
            $this->setVariable($key, $value);
532
        }
533
534
        return $this;
535
    }
536
537
    /**
538
     * Get all variables.
539
     *
540
     * @return array
541
     */
542
    public function getVariables(): array
543
    {
544
        return $this->properties;
545
    }
546
547
    /**
548
     * Set a variable.
549
     *
550
     * @param $name
551
     * @param $value
552
     *
553
     * @throws \Exception
554
     *
555
     * @return $this
556
     */
557
    public function setVariable($name, $value)
558
    {
559
        if (is_bool($value)) {
560
            $value = $value ?: 0;
561
        }
562
        switch ($name) {
563
            case 'date':
564
                try {
565
                    $date = Util::dateToDatetime($value);
566
                } catch (\Exception $e) {
567
                    throw new \Exception(sprintf(
568
                        'Expected date format (ie: "2012-10-08") for "date" in "%s" instead of "%s"',
569
                        $this->getId(),
570
                        $value
571
                    ));
572
                }
573
                $this->offsetSet('date', $date);
574
                break;
575
            case 'draft':
576
                if ($value === true) {
577
                    $this->offsetSet('published', false);
578
                }
579
                break;
580
            case 'path':
581
            case 'slug':
582
                $slugify = self::slugify($value);
583
                if ($value != $slugify) {
584
                    throw new \Exception(sprintf(
585
                        '"%s" variable should be "%s" (not "%s") in "%s"',
586
                        $name,
587
                        $slugify,
588
                        $value,
589
                        $this->getId()
590
                    ));
591
                }
592
                // @see setPath()
593
                // @see setSlug()
594
                $method = 'set'.\ucfirst($name);
595
                $this->$method($value);
596
                break;
597
            default:
598
                $this->offsetSet($name, $value);
599
        }
600
601
        return $this;
602
    }
603
604
    /**
605
     * Is variable exist?
606
     *
607
     * @param string $name
608
     *
609
     * @return bool
610
     */
611
    public function hasVariable(string $name): bool
612
    {
613
        return $this->offsetExists($name);
614
    }
615
616
    /**
617
     * Get a variable.
618
     *
619
     * @param string $name
620
     *
621
     * @return mixed|null
622
     */
623
    public function getVariable(string $name)
624
    {
625
        if ($this->offsetExists($name)) {
626
            return $this->offsetGet($name);
627
        }
628
    }
629
630
    /**
631
     * Unset a variable.
632
     *
633
     * @param string $name
634
     *
635
     * @return $this
636
     */
637
    public function unVariable(string $name): self
638
    {
639
        if ($this->offsetExists($name)) {
640
            $this->offsetUnset($name);
641
        }
642
643
        return $this;
644
    }
645
}
646