Passed
Push — nested-sections ( 54d13d...96df21 )
by Arnaud
03:28
created

Page::setVariable()   C

Complexity

Conditions 15
Paths 16

Size

Total Lines 49
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 15.59

Importance

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