Passed
Push — 8.x-dev ( 640d34...2250ec )
by Arnaud
04:51 queued 16s
created

Page::setVariable()   C

Complexity

Conditions 17
Paths 18

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 17.0587

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