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