Passed
Push — fix ( 82edae )
by Arnaud
05:13
created

Page::getVariables()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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