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