Passed
Pull Request — master (#1704)
by Arnaud
22:27 queued 14:59
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
        $fileExtension = self::getFileComponents($file)['ext'];
0 ignored issues
show
Unused Code introduced by
The assignment to $fileExtension is dead and can be removed.
Loading history...
169 1
        $fileName = self::getFileComponents($file)['name'];
170
        /*
171
         * Set page properties and variables
172
         */
173 1
        $this->setFolder($fileRelativePath);
174 1
        $this->setSlug($fileName);
175 1
        $this->setPath($this->getFolder() . '/' . $this->getSlug());
176 1
        $this->setVariables([
177 1
            'title'    => PrefixSuffix::sub($fileName),
178 1
            'date'     => (new \DateTime())->setTimestamp($this->file->getMTime()),
179 1
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
180 1
            'filepath' => $this->file->getRelativePathname(),
181 1
        ]);
182
        // is a section?
183 1
        if (PrefixSuffix::sub($fileName) == 'index') {
184 1
            $this->setType(Type::SECTION->value);
185 1
            $this->setVariable('title', ucfirst(explode('/', $fileRelativePath)[\count(explode('/', $fileRelativePath)) - 1]));
186
            // is the home page?
187 1
            if (empty($this->getFolder())) {
188 1
                $this->setType(Type::HOMEPAGE->value);
189 1
                $this->setVariable('title', 'Homepage');
190
            }
191
        }
192
        // is file has a prefix?
193 1
        if (PrefixSuffix::hasPrefix($fileName)) {
194 1
            $prefix = PrefixSuffix::getPrefix($fileName);
195 1
            if ($prefix !== null) {
196
                // prefix is a valid date?
197 1
                if (Util\Date::isValid($prefix)) {
198 1
                    $this->setVariable('date', (string) $prefix);
199
                } else {
200
                    // prefix is an integer: used for sorting
201 1
                    $this->setVariable('weight', (int) $prefix);
202
                }
203
            }
204
        }
205
        // is file has a language suffix?
206 1
        if (PrefixSuffix::hasSuffix($fileName)) {
207 1
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
208
        }
209
        // set reference between page's translations, even if it exist in only one language
210 1
        $this->setVariable('langref', $this->getPath());
211
212 1
        return $this;
213
    }
214
215
    /**
216
     * Returns file real path.
217
     */
218 1
    public function getFilePath(): ?string
219
    {
220 1
        if ($this->file === null) {
221
            return null;
222
        }
223
224 1
        return $this->file->getRealPath() === false ? null : $this->file->getRealPath();
225
    }
226
227
    /**
228
     * Parse file content.
229
     */
230 1
    public function parse(): self
231
    {
232 1
        $parser = new Parser($this->file);
233 1
        $parsed = $parser->parse();
234 1
        $this->frontmatter = $parsed->getFrontmatter();
235 1
        $this->body = $parsed->getBody();
236
237 1
        return $this;
238
    }
239
240
    /**
241
     * Get front matter.
242
     */
243 1
    public function getFrontmatter(): ?string
244
    {
245 1
        return $this->frontmatter;
246
    }
247
248
    /**
249
     * Get body as raw.
250
     */
251 1
    public function getBody(): ?string
252
    {
253 1
        return $this->body;
254
    }
255
256
    /**
257
     * Set virtual status.
258
     */
259 1
    public function setVirtual(bool $virtual): self
260
    {
261 1
        $this->virtual = $virtual;
262
263 1
        return $this;
264
    }
265
266
    /**
267
     * Is current page is virtual?
268
     */
269 1
    public function isVirtual(): bool
270
    {
271 1
        return $this->virtual;
272
    }
273
274
    /**
275
     * Set page type.
276
     */
277 1
    public function setType(string $type): self
278
    {
279 1
        $this->type = Type::from($type);
280
281 1
        return $this;
282
    }
283
284
    /**
285
     * Get page type.
286
     */
287 1
    public function getType(): string
288
    {
289 1
        return $this->type->value;
290
    }
291
292
    /**
293
     * Set path without slug.
294
     */
295 1
    public function setFolder(string $folder): self
296
    {
297 1
        $this->folder = self::slugify($folder);
298
299 1
        return $this;
300
    }
301
302
    /**
303
     * Get path without slug.
304
     */
305 1
    public function getFolder(): ?string
306
    {
307 1
        return $this->folder;
308
    }
309
310
    /**
311
     * Set slug.
312
     */
313 1
    public function setSlug(string $slug): self
314
    {
315 1
        if (!$this->slug) {
316 1
            $slug = self::slugify(PrefixSuffix::sub($slug));
317
        }
318
        // force slug and update path
319 1
        if ($this->slug && $this->slug != $slug) {
320 1
            $this->setPath($this->getFolder() . '/' . $slug);
321
        }
322 1
        $this->slug = $slug;
323
324 1
        return $this;
325
    }
326
327
    /**
328
     * Get slug.
329
     */
330 1
    public function getSlug(): string
331
    {
332 1
        return $this->slug;
333
    }
334
335
    /**
336
     * Set path.
337
     */
338 1
    public function setPath(string $path): self
339
    {
340 1
        $path = trim($path, '/');
341
        // case of homepage
342 1
        if ($path == 'index') {
343 1
            $this->path = '';
344 1
            return $this;
345
        }
346
        // case of custom sections' index (ie: section/index.md -> section)
347 1
        if (substr($path, -6) == '/index') {
348 1
            $path = substr($path, 0, \strlen($path) - 6);
349
        }
350 1
        $this->path = $path;
351 1
        $lastslash = strrpos($this->path, '/');
352
        // case of root/top-level pages
353 1
        if ($lastslash === false) {
354 1
            $this->slug = $this->path;
355 1
            return $this;
356
        }
357
        // case of sections' pages: set section
358 1
        if (!$this->virtual && $this->getSection() === null) {
359 1
            $this->section = explode('/', $this->path)[0];
360
        }
361
        // set/update folder and slug
362 1
        $this->folder = substr($this->path, 0, $lastslash);
363 1
        $this->slug = substr($this->path, -(\strlen($this->path) - $lastslash - 1));
364 1
        return $this;
365
    }
366
367
    /**
368
     * Get path.
369
     */
370 1
    public function getPath(): ?string
371
    {
372 1
        return $this->path;
373
    }
374
375
    /**
376
     * @see getPath()
377
     */
378
    public function getPathname(): ?string
379
    {
380
        return $this->getPath();
381
    }
382
383
    /**
384
     * Set section.
385
     */
386 1
    public function setSection(string $section): self
387
    {
388 1
        $this->section = $section;
389
390 1
        return $this;
391
    }
392
393
    /**
394
     * Get section.
395
     */
396 1
    public function getSection(): ?string
397
    {
398 1
        return !empty($this->section) ? $this->section : null;
399
    }
400
401
    /**
402
     * Unset section.
403
     */
404
    public function unSection(): self
405
    {
406
        $this->section = null;
407
408
        return $this;
409
    }
410
411
    /**
412
     * Set body as HTML.
413
     */
414 1
    public function setBodyHtml(string $html): self
415
    {
416 1
        $this->html = $html;
417
418 1
        return $this;
419
    }
420
421
    /**
422
     * Get body as HTML.
423
     */
424 1
    public function getBodyHtml(): ?string
425
    {
426 1
        return $this->html;
427
    }
428
429
    /**
430
     * @see getBodyHtml()
431
     */
432 1
    public function getContent(): ?string
433
    {
434 1
        return $this->getBodyHtml();
435
    }
436
437
    /**
438
     * Add rendered.
439
     */
440 1
    public function addRendered(array $rendered): self
441
    {
442 1
        $this->rendered += $rendered;
443
444 1
        return $this;
445
    }
446
447
    /**
448
     * Get rendered.
449
     */
450 1
    public function getRendered(): array
451
    {
452 1
        return $this->rendered;
453
    }
454
455
    /**
456
     * Set Subpages.
457
     */
458 1
    public function setPages(\Cecil\Collection\Page\Collection $subPages): self
459
    {
460 1
        $this->subPages = $subPages;
461
462 1
        return $this;
463
    }
464
465
    /**
466
     * Get Subpages.
467
     */
468 1
    public function getPages(): ?\Cecil\Collection\Page\Collection
469
    {
470 1
        return $this->subPages;
471
    }
472
473
    /**
474
     * Set paginator.
475
     */
476 1
    public function setPaginator(array $paginator): self
477
    {
478 1
        $this->paginator = $paginator;
479
480 1
        return $this;
481
    }
482
483
    /**
484
     * Get paginator.
485
     */
486 1
    public function getPaginator(): array
487
    {
488 1
        return $this->paginator;
489
    }
490
491
    /**
492
     * Paginator backward compatibility.
493
     */
494
    public function getPagination(): array
495
    {
496
        return $this->getPaginator();
497
    }
498
499
    /**
500
     * Set vocabulary terms.
501
     */
502 1
    public function setTerms(\Cecil\Collection\Taxonomy\Vocabulary $terms): self
503
    {
504 1
        $this->terms = $terms;
505
506 1
        return $this;
507
    }
508
509
    /**
510
     * Get vocabulary terms.
511
     */
512 1
    public function getTerms(): \Cecil\Collection\Taxonomy\Vocabulary
513
    {
514 1
        return $this->terms;
515
    }
516
517
    /*
518
     * Helpers to set and get variables.
519
     */
520
521
    /**
522
     * Set an array as variables.
523
     *
524
     * @throws RuntimeException
525
     */
526 1
    public function setVariables(array $variables): self
527
    {
528 1
        foreach ($variables as $key => $value) {
529 1
            $this->setVariable($key, $value);
530
        }
531
532 1
        return $this;
533
    }
534
535
    /**
536
     * Get all variables.
537
     */
538 1
    public function getVariables(): array
539
    {
540 1
        return $this->properties;
541
    }
542
543
    /**
544
     * Set a variable.
545
     *
546
     * @param string $name  Name of the variable
547
     * @param mixed  $value Value of the variable
548
     *
549
     * @throws RuntimeException
550
     */
551 1
    public function setVariable(string $name, $value): self
552
    {
553 1
        $this->filterBool($value);
554
        switch ($name) {
555 1
            case 'date':
556 1
            case 'updated':
557 1
            case 'lastmod':
558
                try {
559 1
                    $date = Util\Date::toDatetime($value);
560
                } catch (\Exception) {
561
                    throw new \Exception(sprintf('The value of "%s" is not a valid date: "%s".', $name, var_export($value, true)));
562
                }
563 1
                $this->offsetSet($name == 'lastmod' ? 'updated' : $name, $date);
564 1
                break;
565
566 1
            case 'schedule':
567
                /*
568
                 * publish: 2012-10-08
569
                 * expiry: 2012-10-09
570
                 */
571 1
                $this->offsetSet('published', false);
572 1
                if (\is_array($value)) {
573 1
                    if (\array_key_exists('publish', $value) && Util\Date::toDatetime($value['publish']) <= Util\Date::toDatetime('now')) {
574 1
                        $this->offsetSet('published', true);
575
                    }
576 1
                    if (\array_key_exists('expiry', $value) && Util\Date::toDatetime($value['expiry']) >= Util\Date::toDatetime('now')) {
577
                        $this->offsetSet('published', true);
578
                    }
579
                }
580 1
                break;
581 1
            case 'draft':
582
                // draft: true = published: false
583 1
                if ($value === true) {
584 1
                    $this->offsetSet('published', false);
585
                }
586 1
                break;
587 1
            case 'path':
588 1
            case 'slug':
589 1
                $slugify = self::slugify((string) $value);
590 1
                if ($value != $slugify) {
591
                    throw new RuntimeException(sprintf('"%s" variable should be "%s" (not "%s") in "%s".', $name, $slugify, (string) $value, $this->getId()));
592
                }
593 1
                $method = 'set' . ucfirst($name);
594 1
                $this->$method($value);
595 1
                break;
596
            default:
597 1
                $this->offsetSet($name, $value);
598
        }
599
600 1
        return $this;
601
    }
602
603
    /**
604
     * Is variable exists?
605
     *
606
     * @param string $name Name of the variable
607
     */
608 1
    public function hasVariable(string $name): bool
609
    {
610 1
        return $this->offsetExists($name);
611
    }
612
613
    /**
614
     * Get a variable.
615
     *
616
     * @param string     $name    Name of the variable
617
     * @param mixed|null $default Default value
618
     *
619
     * @return mixed|null
620
     */
621 1
    public function getVariable(string $name, $default = null)
622
    {
623 1
        if ($this->offsetExists($name)) {
624 1
            return $this->offsetGet($name);
625
        }
626
627 1
        return $default;
628
    }
629
630
    /**
631
     * Unset a variable.
632
     *
633
     * @param string $name Name of the variable
634
     */
635 1
    public function unVariable(string $name): self
636
    {
637 1
        if ($this->offsetExists($name)) {
638 1
            $this->offsetUnset($name);
639
        }
640
641 1
        return $this;
642
    }
643
644
    /**
645
     * Set front matter (only) variables.
646
     */
647 1
    public function setFmVariables(array $variables): self
648
    {
649 1
        $this->fmVariables = $variables;
650
651 1
        return $this;
652
    }
653
654
    /**
655
     * Get front matter variables.
656
     */
657 1
    public function getFmVariables(): array
658
    {
659 1
        return $this->fmVariables;
660
    }
661
662
    /**
663
     * Set parent page.
664
     */
665 1
    public function setParent(self $page): self
666
    {
667 1
        $this->parent = $page;
668
669 1
        return $this;
670
    }
671
672
    /**
673
     * Returns parent page if exists.
674
     */
675 1
    public function getParent(): ?self
676
    {
677 1
        return $this->parent;
678
    }
679
680
    /**
681
     * Returns array of ancestors pages.
682
     */
683 1
    public function getAncestors(): array
684
    {
685 1
        $ancestors = [];
686
687 1
        if (null !== $parent = $this->getParent()) {
688 1
            $ancestors[] = $parent;
689 1
            while (null !== $parent = $parent->getParent()) {
690 1
                $ancestors[] = $parent;
691
            };
692
        }
693
694 1
        return $ancestors;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 1
    public function setId(string $id): self
701
    {
702 1
        return parent::setId($id);
703
    }
704
705
    /**
706
     * Cast "boolean" string (or array of strings) to boolean.
707
     *
708
     * @param mixed $value Value to filter
709
     *
710
     * @return bool|mixed
711
     *
712
     * @see strToBool()
713
     */
714 1
    private function filterBool(&$value)
715
    {
716 1
        \Cecil\Util\Str::strToBool($value);
717 1
        if (\is_array($value)) {
718 1
            array_walk_recursive($value, '\Cecil\Util\Str::strToBool');
719
        }
720
    }
721
722
    /**
723
     * Get file components.
724
     *
725
     * [
726
     *   path => relative path,
727
     *   name => name,
728
     *   ext  => extension,
729
     * ]
730
     */
731 1
    private static function getFileComponents(SplFileInfo $file): array
732
    {
733 1
        return [
734 1
            'path' => str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()),
735 1
            'name' => (string) str_ireplace('readme', 'index', $file->getBasename('.' . $file->getExtension())),
736 1
            'ext'  => $file->getExtension(),
737 1
        ];
738
    }
739
}
740