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