Passed
Pull Request — master (#1704)
by Arnaud
11:53 queued 04:45
created

Page::setVariable()   C

Complexity

Conditions 17
Paths 18

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 17.5644

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 17
eloc 34
c 1
b 0
f 1
nc 18
nop 2
dl 0
loc 50
ccs 28
cts 32
cp 0.875
crap 17.5644
rs 5.2166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    public const SLUGIFY_PATTERN = '/(^\/|[^._a-z0-9\/]|-)+/'; // should be '/^\/|[^_a-z0-9\/]+/'
28
29
    /** @var bool True if page is not created from a file. */
30
    protected $virtual;
31
32
    /** @var SplFileInfo */
33
    protected $file;
34
35
    /** @var Type Type */
36
    protected $type;
37
38
    /** @var string */
39
    protected $folder;
40
41
    /** @var string */
42
    protected $slug;
43
44
    /** @var string path = folder + slug. */
45
    protected $path;
46
47
    /** @var string */
48
    protected $section;
49
50
    /** @var string */
51
    protected $frontmatter;
52
53
    /** @var array Front matter before conversion. */
54
    protected $fmVariables = [];
55
56
    /** @var string Body before conversion. */
57
    protected $body;
58
59
    /** @var string Body after conversion. */
60
    protected $html;
61
62
    /** @var array Output, by format */
63
    protected $rendered = [];
64
65
    /** @var Collection Subpages of a list page. */
66
    protected $subPages;
67
68
    /** @var array */
69
    protected $paginator = [];
70
71
    /** @var \Cecil\Collection\Taxonomy\Vocabulary Terms of a vocabulary. */
72
    protected $terms;
73
74
    /** @var self Parent page of a PAGE page or a SECTION page */
75
    protected $parent;
76
77
    /** @var Slugify */
78
    private static $slugifier;
79
80 1
    public function __construct(string $id)
81
    {
82 1
        parent::__construct($id);
83 1
        $this->setVirtual(true);
84 1
        $this->setType(Type::PAGE->value);
85
        // default variables
86 1
        $this->setVariables([
87 1
            'title'            => 'Page Title',
88 1
            'date'             => new \DateTime(),
89 1
            'updated'          => new \DateTime(),
90 1
            'weight'           => null,
91 1
            'filepath'         => null,
92 1
            'published'        => true,
93 1
            'content_template' => 'page.content.twig',
94 1
        ]);
95
    }
96
97
    /**
98
     * toString magic method to prevent Twig get_attribute fatal error.
99
     *
100
     * @return string
101
     */
102 1
    public function __toString()
103
    {
104 1
        return $this->getId();
105
    }
106
107
    /**
108
     * Turns a path (string) into a slug (URI).
109
     */
110 1
    public static function slugify(string $path): string
111
    {
112 1
        if (!self::$slugifier instanceof Slugify) {
113 1
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
114
        }
115
116 1
        return self::$slugifier->slugify($path);
117
    }
118
119
    /**
120
     * Creates the ID from the file (path).
121
     */
122 1
    public static function createIdFromFile(SplFileInfo $file): string
123
    {
124 1
        $relativePath = self::slugify(self::getFileComponents($file)['path']);
125 1
        $basename = self::slugify(PrefixSuffix::subPrefix(self::getFileComponents($file)['name']));
126
        // if file is a section's index: "<section>/index.md", "<section>" is the ID
127 1
        if (!empty($relativePath) && PrefixSuffix::sub($basename) == 'index') {
128
            // case of a localized section's index: "<section>/index.fr.md", "<fr/section>" is the ID
129 1
            if (PrefixSuffix::hasSuffix($basename)) {
130 1
                return PrefixSuffix::getSuffix($basename) . '/' . $relativePath;
131
            }
132
133 1
            return $relativePath;
134
        }
135
        // localized page: "<page>.fr.md" -> "fr/<page>"
136 1
        if (PrefixSuffix::hasSuffix($basename)) {
137 1
            return trim(Util::joinPath(PrefixSuffix::getSuffix($basename), $relativePath, PrefixSuffix::sub($basename)), '/');
138
        }
139
140 1
        return trim(Util::joinPath($relativePath, $basename), '/');
141
    }
142
143
    /**
144
     * Returns the ID of a page without language.
145
     */
146 1
    public function getIdWithoutLang(): string
147
    {
148 1
        $langPrefix = $this->getVariable('language') . '/';
149 1
        if ($this->hasVariable('language') && Util\Str::startsWith($this->getId(), $langPrefix)) {
150 1
            return substr($this->getId(), \strlen($langPrefix));
151
        }
152
153 1
        return $this->getId();
154
    }
155
156
    /**
157
     * Set file.
158
     */
159 1
    public function setFile(SplFileInfo $file): self
160
    {
161 1
        $this->file = $file;
162 1
        $this->setVirtual(false);
163
164
        /*
165
         * File path components
166
         */
167 1
        $fileRelativePath = self::getFileComponents($file)['path'];
168 1
        $fileName = self::getFileComponents($file)['name'];
169
        /*
170
         * Set page properties and variables
171
         */
172 1
        $this->setFolder($fileRelativePath);
173 1
        $this->setSlug($fileName);
174 1
        $this->setPath($this->getFolder() . '/' . $this->getSlug());
175 1
        $this->setVariables([
176 1
            'title'    => PrefixSuffix::sub($fileName),
177 1
            'date'     => (new \DateTime())->setTimestamp($this->file->getMTime()),
178 1
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
179 1
            'filepath' => $this->file->getRelativePathname(),
180 1
        ]);
181
        // is a section?
182 1
        if (PrefixSuffix::sub($fileName) == 'index') {
183 1
            $this->setType(Type::SECTION->value);
184 1
            $this->setVariable('title', ucfirst(explode('/', $fileRelativePath)[\count(explode('/', $fileRelativePath)) - 1]));
185
            // is the home page?
186 1
            if (empty($this->getFolder())) {
187 1
                $this->setType(Type::HOMEPAGE->value);
188 1
                $this->setVariable('title', 'Homepage');
189
            }
190
        }
191
        // is file has a prefix?
192 1
        if (PrefixSuffix::hasPrefix($fileName)) {
193 1
            $prefix = PrefixSuffix::getPrefix($fileName);
194 1
            if ($prefix !== null) {
195
                // prefix is a valid date?
196 1
                if (Util\Date::isValid($prefix)) {
197 1
                    $this->setVariable('date', (string) $prefix);
198
                } else {
199
                    // prefix is an integer: used for sorting
200 1
                    $this->setVariable('weight', (int) $prefix);
201
                }
202
            }
203
        }
204
        // is file has a language suffix?
205 1
        if (PrefixSuffix::hasSuffix($fileName)) {
206 1
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
207
        }
208
        // set reference between page's translations, even if it exist in only one language
209 1
        $this->setVariable('langref', $this->getPath());
210
211 1
        return $this;
212
    }
213
214
    /**
215
     * Returns file real path.
216
     */
217 1
    public function getFilePath(): ?string
218
    {
219 1
        if ($this->file === null) {
220
            return null;
221
        }
222
223 1
        return $this->file->getRealPath() === false ? null : $this->file->getRealPath();
224
    }
225
226
    /**
227
     * Parse file content.
228
     */
229 1
    public function parse(): self
230
    {
231 1
        $parser = new Parser($this->file);
232 1
        $parsed = $parser->parse();
233 1
        $this->frontmatter = $parsed->getFrontmatter();
234 1
        $this->body = $parsed->getBody();
235
236 1
        return $this;
237
    }
238
239
    /**
240
     * Get front matter.
241
     */
242 1
    public function getFrontmatter(): ?string
243
    {
244 1
        return $this->frontmatter;
245
    }
246
247
    /**
248
     * Get body as raw.
249
     */
250 1
    public function getBody(): ?string
251
    {
252 1
        return $this->body;
253
    }
254
255
    /**
256
     * Set virtual status.
257
     */
258 1
    public function setVirtual(bool $virtual): self
259
    {
260 1
        $this->virtual = $virtual;
261
262 1
        return $this;
263
    }
264
265
    /**
266
     * Is current page is virtual?
267
     */
268 1
    public function isVirtual(): bool
269
    {
270 1
        return $this->virtual;
271
    }
272
273
    /**
274
     * Set page type.
275
     */
276 1
    public function setType(string $type): self
277
    {
278 1
        $this->type = Type::from($type);
279
280 1
        return $this;
281
    }
282
283
    /**
284
     * Get page type.
285
     */
286 1
    public function getType(): string
287
    {
288 1
        return $this->type->value;
289
    }
290
291
    /**
292
     * Set path without slug.
293
     */
294 1
    public function setFolder(string $folder): self
295
    {
296 1
        $this->folder = self::slugify($folder);
297
298 1
        return $this;
299
    }
300
301
    /**
302
     * Get path without slug.
303
     */
304 1
    public function getFolder(): ?string
305
    {
306 1
        return $this->folder;
307
    }
308
309
    /**
310
     * Set slug.
311
     */
312 1
    public function setSlug(string $slug): self
313
    {
314 1
        if (!$this->slug) {
315 1
            $slug = self::slugify(PrefixSuffix::sub($slug));
316
        }
317
        // force slug and update path
318 1
        if ($this->slug && $this->slug != $slug) {
319 1
            $this->setPath($this->getFolder() . '/' . $slug);
320
        }
321 1
        $this->slug = $slug;
322
323 1
        return $this;
324
    }
325
326
    /**
327
     * Get slug.
328
     */
329 1
    public function getSlug(): string
330
    {
331 1
        return $this->slug;
332
    }
333
334
    /**
335
     * Set path.
336
     */
337 1
    public function setPath(string $path): self
338
    {
339 1
        $path = trim($path, '/');
340
        // case of homepage
341 1
        if ($path == 'index') {
342 1
            $this->path = '';
343 1
            return $this;
344
        }
345
        // case of custom sections' index (ie: section/index.md -> section)
346 1
        if (substr($path, -6) == '/index') {
347 1
            $path = substr($path, 0, \strlen($path) - 6);
348
        }
349 1
        $this->path = $path;
350 1
        $lastslash = strrpos($this->path, '/');
351
        // case of root/top-level pages
352 1
        if ($lastslash === false) {
353 1
            $this->slug = $this->path;
354 1
            return $this;
355
        }
356
        // case of sections' pages: set section
357 1
        if (!$this->virtual && $this->getSection() === null) {
358 1
            $this->section = explode('/', $this->path)[0];
359
        }
360
        // set/update folder and slug
361 1
        $this->folder = substr($this->path, 0, $lastslash);
362 1
        $this->slug = substr($this->path, -(\strlen($this->path) - $lastslash - 1));
363 1
        return $this;
364
    }
365
366
    /**
367
     * Get path.
368
     */
369 1
    public function getPath(): ?string
370
    {
371 1
        return $this->path;
372
    }
373
374
    /**
375
     * @see getPath()
376
     */
377
    public function getPathname(): ?string
378
    {
379
        return $this->getPath();
380
    }
381
382
    /**
383
     * Set section.
384
     */
385 1
    public function setSection(string $section): self
386
    {
387 1
        $this->section = $section;
388
389 1
        return $this;
390
    }
391
392
    /**
393
     * Get section.
394
     */
395 1
    public function getSection(): ?string
396
    {
397 1
        return !empty($this->section) ? $this->section : null;
398
    }
399
400
    /**
401
     * Unset section.
402
     */
403
    public function unSection(): self
404
    {
405
        $this->section = null;
406
407
        return $this;
408
    }
409
410
    /**
411
     * Set body as HTML.
412
     */
413 1
    public function setBodyHtml(string $html): self
414
    {
415 1
        $this->html = $html;
416
417 1
        return $this;
418
    }
419
420
    /**
421
     * Get body as HTML.
422
     */
423 1
    public function getBodyHtml(): ?string
424
    {
425 1
        return $this->html;
426
    }
427
428
    /**
429
     * @see getBodyHtml()
430
     */
431 1
    public function getContent(): ?string
432
    {
433 1
        return $this->getBodyHtml();
434
    }
435
436
    /**
437
     * Add rendered.
438
     */
439 1
    public function addRendered(array $rendered): self
440
    {
441 1
        $this->rendered += $rendered;
442
443 1
        return $this;
444
    }
445
446
    /**
447
     * Get rendered.
448
     */
449 1
    public function getRendered(): array
450
    {
451 1
        return $this->rendered;
452
    }
453
454
    /**
455
     * Set Subpages.
456
     */
457 1
    public function setPages(\Cecil\Collection\Page\Collection $subPages): self
458
    {
459 1
        $this->subPages = $subPages;
460
461 1
        return $this;
462
    }
463
464
    /**
465
     * Get Subpages.
466
     */
467 1
    public function getPages(): ?\Cecil\Collection\Page\Collection
468
    {
469 1
        return $this->subPages;
470
    }
471
472
    /**
473
     * Set paginator.
474
     */
475 1
    public function setPaginator(array $paginator): self
476
    {
477 1
        $this->paginator = $paginator;
478
479 1
        return $this;
480
    }
481
482
    /**
483
     * Get paginator.
484
     */
485 1
    public function getPaginator(): array
486
    {
487 1
        return $this->paginator;
488
    }
489
490
    /**
491
     * Paginator backward compatibility.
492
     */
493
    public function getPagination(): array
494
    {
495
        return $this->getPaginator();
496
    }
497
498
    /**
499
     * Set vocabulary terms.
500
     */
501 1
    public function setTerms(\Cecil\Collection\Taxonomy\Vocabulary $terms): self
502
    {
503 1
        $this->terms = $terms;
504
505 1
        return $this;
506
    }
507
508
    /**
509
     * Get vocabulary terms.
510
     */
511 1
    public function getTerms(): \Cecil\Collection\Taxonomy\Vocabulary
512
    {
513 1
        return $this->terms;
514
    }
515
516
    /*
517
     * Helpers to set and get variables.
518
     */
519
520
    /**
521
     * Set an array as variables.
522
     *
523
     * @throws RuntimeException
524
     */
525 1
    public function setVariables(array $variables): self
526
    {
527 1
        foreach ($variables as $key => $value) {
528 1
            $this->setVariable($key, $value);
529
        }
530
531 1
        return $this;
532
    }
533
534
    /**
535
     * Get all variables.
536
     */
537 1
    public function getVariables(): array
538
    {
539 1
        return $this->properties;
540
    }
541
542
    /**
543
     * Set a variable.
544
     *
545
     * @param string $name  Name of the variable
546
     * @param mixed  $value Value of the variable
547
     *
548
     * @throws RuntimeException
549
     */
550 1
    public function setVariable(string $name, $value): self
551
    {
552 1
        $this->filterBool($value);
553
        switch ($name) {
554 1
            case 'date':
555 1
            case 'updated':
556 1
            case 'lastmod':
557
                try {
558 1
                    $date = Util\Date::toDatetime($value);
559
                } catch (\Exception) {
560
                    throw new \Exception(sprintf('The value of "%s" is not a valid date: "%s".', $name, var_export($value, true)));
561
                }
562 1
                $this->offsetSet($name == 'lastmod' ? 'updated' : $name, $date);
563 1
                break;
564
565 1
            case 'schedule':
566
                /*
567
                 * publish: 2012-10-08
568
                 * expiry: 2012-10-09
569
                 */
570 1
                $this->offsetSet('published', false);
571 1
                if (\is_array($value)) {
572 1
                    if (\array_key_exists('publish', $value) && Util\Date::toDatetime($value['publish']) <= Util\Date::toDatetime('now')) {
573 1
                        $this->offsetSet('published', true);
574
                    }
575 1
                    if (\array_key_exists('expiry', $value) && Util\Date::toDatetime($value['expiry']) >= Util\Date::toDatetime('now')) {
576
                        $this->offsetSet('published', true);
577
                    }
578
                }
579 1
                break;
580 1
            case 'draft':
581
                // draft: true = published: false
582 1
                if ($value === true) {
583 1
                    $this->offsetSet('published', false);
584
                }
585 1
                break;
586 1
            case 'path':
587 1
            case 'slug':
588 1
                $slugify = self::slugify((string) $value);
589 1
                if ($value != $slugify) {
590
                    throw new RuntimeException(sprintf('"%s" variable should be "%s" (not "%s") in "%s".', $name, $slugify, (string) $value, $this->getId()));
591
                }
592 1
                $method = 'set' . ucfirst($name);
593 1
                $this->$method($value);
594 1
                break;
595
            default:
596 1
                $this->offsetSet($name, $value);
597
        }
598
599 1
        return $this;
600
    }
601
602
    /**
603
     * Is variable exists?
604
     *
605
     * @param string $name Name of the variable
606
     */
607 1
    public function hasVariable(string $name): bool
608
    {
609 1
        return $this->offsetExists($name);
610
    }
611
612
    /**
613
     * Get a variable.
614
     *
615
     * @param string     $name    Name of the variable
616
     * @param mixed|null $default Default value
617
     *
618
     * @return mixed|null
619
     */
620 1
    public function getVariable(string $name, $default = null)
621
    {
622 1
        if ($this->offsetExists($name)) {
623 1
            return $this->offsetGet($name);
624
        }
625
626 1
        return $default;
627
    }
628
629
    /**
630
     * Unset a variable.
631
     *
632
     * @param string $name Name of the variable
633
     */
634 1
    public function unVariable(string $name): self
635
    {
636 1
        if ($this->offsetExists($name)) {
637 1
            $this->offsetUnset($name);
638
        }
639
640 1
        return $this;
641
    }
642
643
    /**
644
     * Set front matter (only) variables.
645
     */
646 1
    public function setFmVariables(array $variables): self
647
    {
648 1
        $this->fmVariables = $variables;
649
650 1
        return $this;
651
    }
652
653
    /**
654
     * Get front matter variables.
655
     */
656 1
    public function getFmVariables(): array
657
    {
658 1
        return $this->fmVariables;
659
    }
660
661
    /**
662
     * Set parent page.
663
     */
664 1
    public function setParent(self $page): self
665
    {
666 1
        $this->parent = $page;
667
668 1
        return $this;
669
    }
670
671
    /**
672
     * Returns parent page if exists.
673
     */
674 1
    public function getParent(): ?self
675
    {
676 1
        return $this->parent;
677
    }
678
679
    /**
680
     * Returns array of ancestors pages.
681
     */
682 1
    public function getAncestors(): array
683
    {
684 1
        $ancestors = [];
685 1
        $currentPage = $this;
686 1
        while ($currentPage->getParent() !== null) {
687 1
            $ancestors[] = $currentPage = $currentPage->getParent();
688
        }
689
690 1
        return $ancestors;
691
    }
692
693
    /**
694
     * {@inheritdoc}
695
     */
696 1
    public function setId(string $id): self
697
    {
698 1
        return parent::setId($id);
699
    }
700
701
    /**
702
     * Cast "boolean" string (or array of strings) to boolean.
703
     *
704
     * @param mixed $value Value to filter
705
     *
706
     * @return bool|mixed
707
     *
708
     * @see strToBool()
709
     */
710 1
    private function filterBool(&$value)
711
    {
712 1
        \Cecil\Util\Str::strToBool($value);
713 1
        if (\is_array($value)) {
714 1
            array_walk_recursive($value, '\Cecil\Util\Str::strToBool');
715
        }
716
    }
717
718
    /**
719
     * Get file components.
720
     *
721
     * [
722
     *   path => relative path,
723
     *   name => name,
724
     *   ext  => extension,
725
     * ]
726
     */
727 1
    private static function getFileComponents(SplFileInfo $file): array
728
    {
729 1
        return [
730 1
            'path' => str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()),
731 1
            'name' => (string) str_ireplace('readme', 'index', $file->getBasename('.' . $file->getExtension())),
732 1
            'ext'  => $file->getExtension(),
733 1
        ];
734
    }
735
}
736