Passed
Push — variables ( 7d01df...502991 )
by Arnaud
07:26 queued 03:49
created

Page::setWeight()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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