Passed
Push — nested-sections ( c50b4c...2d40b2 )
by Arnaud
03:26
created

Page::setVariable()   C

Complexity

Conditions 15
Paths 16

Size

Total Lines 49
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 15.1152

Importance

Changes 0
Metric Value
cc 15
eloc 33
c 0
b 0
f 0
nc 16
nop 2
dl 0
loc 49
ccs 23
cts 25
cp 0.92
crap 15.1152
rs 5.9166

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