Test Failed
Pull Request — master (#1704)
by Arnaud
06:30 queued 22s
created

Page::setVariable()   C

Complexity

Conditions 17
Paths 18

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 17.7578

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