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