Passed
Pull Request — master (#1704)
by Arnaud
08:32 queued 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 27
CRAP Score 15.483

Importance

Changes 0
Metric Value
cc 15
eloc 33
c 0
b 0
f 0
nc 16
nop 2
dl 0
loc 49
ccs 27
cts 31
cp 0.871
crap 15.483
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
115 1
        $fileComponents['path'];
116 1
        $fileComponents['name'];
117 1
        $fileComponents['ext'];
118
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 1
        $basename = (string) str_ireplace('readme', 'index', $basename);
124
        // if file is section's index: "section/index.md", ID is "section"
125 1
        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 1
        if (PrefixSuffix::hasSuffix($basename)) {
135 1
            return trim(Util::joinPath(PrefixSuffix::getSuffix($basename), $relativePath, PrefixSuffix::sub($basename)), '/');
136
        }
137
138 1
        return trim(Util::joinPath($relativePath, $basename), '/');
139
    }
140
141
    /**
142
     * Returns the ID of a page without language.
143
     */
144 1
    public function getIdWithoutLang(): string
145
    {
146 1
        $langPrefix = $this->getVariable('language') . '/';
147 1
        if ($this->hasVariable('language') && Util\Str::startsWith($this->getId(), $langPrefix)) {
148 1
            return substr($this->getId(), \strlen($langPrefix));
149
        }
150
151 1
        return $this->getId();
152
    }
153
154
    /**
155
     * Set file.
156
     */
157 1
    public function setFile(SplFileInfo $file): self
158
    {
159 1
        $this->file = $file;
160 1
        $this->setVirtual(false);
161
162
        /*
163
         * File path components
164
         */
165 1
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
166 1
        $fileExtension = $this->file->getExtension();
167 1
        $fileName = $this->file->getBasename('.' . $fileExtension);
168
        // renames "README" to "index"
169 1
        $fileName = (string) str_ireplace('readme', 'index', $fileName);
170
171
        /*
172
         * Set page properties and variables
173
         */
174 1
        $this->setFolder($fileRelativePath);
175 1
        $this->setSlug($fileName);
176 1
        $this->setPath($this->getFolder() . '/' . $this->getSlug());
177 1
        $this->setVariables([
178 1
            '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 1
        ]);
183
        // is a section?
184 1
        if (PrefixSuffix::sub($fileName) == 'index') {
185 1
            $this->setType(Type::SECTION);
186 1
            $this->setVariable('title', ucfirst(explode('/', $fileRelativePath)[\count(explode('/', $fileRelativePath)) - 1]));
187
            // is the home page?
188 1
            if (empty($this->getFolder())) {
189 1
                $this->setType(Type::HOMEPAGE);
190 1
                $this->setVariable('title', 'Homepage');
191
            }
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
        return $this->file->getRealPath() === false ? null : $this->file->getRealPath();
222
    }
223
224
    /**
225
     * Parse file content.
226
     */
227 1
    public function parse(): self
228
    {
229 1
        $parser = new Parser($this->file);
230 1
        $parsed = $parser->parse();
231 1
        $this->frontmatter = $parsed->getFrontmatter();
232 1
        $this->body = $parsed->getBody();
233
234 1
        return $this;
235
    }
236
237
    /**
238
     * Get front matter.
239
     */
240 1
    public function getFrontmatter(): ?string
241
    {
242 1
        return $this->frontmatter;
243
    }
244
245
    /**
246
     * Get body as raw.
247
     */
248 1
    public function getBody(): ?string
249
    {
250 1
        return $this->body;
251
    }
252
253
    /**
254
     * Set virtual status.
255
     */
256 1
    public function setVirtual(bool $virtual): self
257
    {
258 1
        $this->virtual = $virtual;
259
260 1
        return $this;
261
    }
262
263
    /**
264
     * Is current page is virtual?
265
     */
266 1
    public function isVirtual(): bool
267
    {
268 1
        return $this->virtual;
269
    }
270
271
    /**
272
     * Set page type.
273
     */
274 1
    public function setType(string $type): self
275
    {
276 1
        $this->type = new Type($type);
277
278 1
        return $this;
279
    }
280
281
    /**
282
     * Get page type.
283
     */
284 1
    public function getType(): string
285
    {
286 1
        return (string) $this->type;
287
    }
288
289
    /**
290
     * Set path without slug.
291
     */
292 1
    public function setFolder(string $folder): self
293
    {
294 1
        $this->folder = self::slugify($folder);
295
296 1
        return $this;
297
    }
298
299
    /**
300
     * Get path without slug.
301
     */
302 1
    public function getFolder(): ?string
303
    {
304 1
        return $this->folder;
305
    }
306
307
    /**
308
     * Set slug.
309
     */
310 1
    public function setSlug(string $slug): self
311
    {
312 1
        if (!$this->slug) {
313 1
            $slug = self::slugify(PrefixSuffix::sub($slug));
314
        }
315
        // force slug and update path
316 1
        if ($this->slug && $this->slug != $slug) {
317 1
            $this->setPath($this->getFolder() . '/' . $slug);
318
        }
319 1
        $this->slug = $slug;
320
321 1
        return $this;
322
    }
323
324
    /**
325
     * Get slug.
326
     */
327 1
    public function getSlug(): string
328
    {
329 1
        return $this->slug;
330
    }
331
332
    /**
333
     * Set path.
334
     */
335 1
    public function setPath(string $path): self
336
    {
337 1
        $path = trim($path, '/');
338
339
        // case of homepage
340 1
        if ($path == 'index') {
341 1
            $this->path = '';
342
343 1
            return $this;
344
        }
345
346
        // case of custom sections' index (ie: file: section/index.md -> path: section)
347 1
        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
        // case of root/top-level pages
355 1
        if ($lastslash === false) {
356 1
            $this->slug = $this->path;
357
358 1
            return $this;
359
        }
360
361
        // case of sections' pages: set section
362 1
        if (!$this->virtual && $this->getSection() === null) {
363 1
            $this->section = explode('/', $this->path)[0];
364
        }
365
        // set/update folder and slug
366 1
        $this->folder = substr($this->path, 0, $lastslash);
367 1
        $this->slug = substr($this->path, -(\strlen($this->path) - $lastslash - 1));
368
369 1
        return $this;
370
    }
371
372
    /**
373
     * Get path.
374
     */
375 1
    public function getPath(): ?string
376
    {
377 1
        return $this->path;
378
    }
379
380
    /**
381
     * @see getPath()
382
     */
383
    public function getPathname(): ?string
384
    {
385
        return $this->getPath();
386
    }
387
388
    /**
389
     * Set section.
390
     */
391 1
    public function setSection(string $section): self
392
    {
393 1
        $this->section = $section;
394
395 1
        return $this;
396
    }
397
398
    /**
399
     * Get section.
400
     */
401 1
    public function getSection(): ?string
402
    {
403 1
        return !empty($this->section) ? $this->section : null;
404
    }
405
406
    /**
407
     * Set body as HTML.
408
     */
409 1
    public function setBodyHtml(string $html): self
410
    {
411 1
        $this->html = $html;
412
413 1
        return $this;
414
    }
415
416
    /**
417
     * Get body as HTML.
418
     */
419 1
    public function getBodyHtml(): ?string
420
    {
421 1
        return $this->html;
422
    }
423
424
    /**
425
     * @see getBodyHtml()
426
     */
427 1
    public function getContent(): ?string
428
    {
429 1
        return $this->getBodyHtml();
430
    }
431
432
    /**
433
     * Add rendered.
434
     */
435 1
    public function addRendered(array $rendered): self
436
    {
437 1
        $this->rendered += $rendered;
438
439 1
        return $this;
440
    }
441
442
    /**
443
     * Get rendered.
444
     */
445 1
    public function getRendered(): array
446
    {
447 1
        return $this->rendered;
448
    }
449
450
    /**
451
     * Set Subpages.
452
     */
453 1
    public function setPages(\Cecil\Collection\Page\Collection $subPages): self
454
    {
455 1
        $this->subPages = $subPages;
456
457 1
        return $this;
458
    }
459
460
    /**
461
     * Get Subpages.
462
     */
463 1
    public function getPages(): ?\Cecil\Collection\Page\Collection
464
    {
465 1
        return $this->subPages;
466
    }
467
468
    /**
469
     * Set paginator.
470
     */
471 1
    public function setPaginator(array $paginator): self
472
    {
473 1
        $this->paginator = $paginator;
474
475 1
        return $this;
476
    }
477
478
    /**
479
     * Get paginator.
480
     */
481 1
    public function getPaginator(): array
482
    {
483 1
        return $this->paginator;
484
    }
485
486
    /**
487
     * Paginator backward compatibility.
488
     */
489
    public function getPagination(): array
490
    {
491
        return $this->getPaginator();
492
    }
493
494
    /**
495
     * Set vocabulary terms.
496
     */
497 1
    public function setTerms(\Cecil\Collection\Taxonomy\Vocabulary $terms): self
498
    {
499 1
        $this->terms = $terms;
500
501 1
        return $this;
502
    }
503
504
    /**
505
     * Get vocabulary terms.
506
     */
507 1
    public function getTerms(): \Cecil\Collection\Taxonomy\Vocabulary
508
    {
509 1
        return $this->terms;
510
    }
511
512
    /*
513
     * Helpers to set and get variables.
514
     */
515
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 1
            $this->setVariable($key, $value);
525
        }
526
527 1
        return $this;
528
    }
529
530
    /**
531
     * Get all variables.
532
     */
533 1
    public function getVariables(): array
534
    {
535 1
        return $this->properties;
536
    }
537
538
    /**
539
     * Set a variable.
540
     *
541
     * @param string $name  Name of the variable
542
     * @param mixed  $value Value of the variable
543
     *
544
     * @throws RuntimeException
545
     */
546 1
    public function setVariable(string $name, $value): self
547
    {
548 1
        $this->filterBool($value);
549
        switch ($name) {
550 1
            case 'date':
551 1
            case 'updated':
552
                try {
553 1
                    $date = Util\Date::toDatetime($value);
554
                } catch (\Exception $e) {
555
                    throw new \Exception(sprintf('Expected date format for variable "%s" must be "YYYY-MM-DD" instead of "%s".', $name, (string) $value));
556
                }
557 1
                $this->offsetSet($name, $date);
558 1
                break;
559
560 1
            case 'schedule':
561
                /*
562
                 * publish: 2012-10-08
563
                 * expiry: 2012-10-09
564
                 */
565 1
                $this->offsetSet('published', false);
566 1
                if (\is_array($value)) {
567 1
                    if (\array_key_exists('publish', $value) && Util\Date::toDatetime($value['publish']) <= Util\Date::toDatetime('now')) {
568 1
                        $this->offsetSet('published', true);
569
                    }
570 1
                    if (\array_key_exists('expiry', $value) && Util\Date::toDatetime($value['expiry']) >= Util\Date::toDatetime('now')) {
571
                        $this->offsetSet('published', true);
572
                    }
573
                }
574 1
                break;
575 1
            case 'draft':
576
                // draft: true = published: false
577 1
                if ($value === true) {
578 1
                    $this->offsetSet('published', false);
579
                }
580 1
                break;
581 1
            case 'path':
582 1
            case 'slug':
583 1
                $slugify = self::slugify((string) $value);
584 1
                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 1
                $method = 'set' . ucfirst($name);
588 1
                $this->$method($value);
589 1
                break;
590
            default:
591 1
                $this->offsetSet($name, $value);
592
        }
593
594 1
        return $this;
595
    }
596
597
    /**
598
     * Is variable exists?
599
     *
600
     * @param string $name Name of the variable
601
     */
602 1
    public function hasVariable(string $name): bool
603
    {
604 1
        return $this->offsetExists($name);
605
    }
606
607
    /**
608
     * Get a variable.
609
     *
610
     * @param string     $name    Name of the variable
611
     * @param mixed|null $default Default value
612
     *
613
     * @return mixed|null
614
     */
615 1
    public function getVariable(string $name, $default = null)
616
    {
617 1
        if ($this->offsetExists($name)) {
618 1
            return $this->offsetGet($name);
619
        }
620
621 1
        return $default;
622
    }
623
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 1
            $this->offsetUnset($name);
633
        }
634
635 1
        return $this;
636
    }
637
638
    /**
639
     * Set front matter (only) variables.
640
     */
641 1
    public function setFmVariables(array $variables): self
642
    {
643 1
        $this->fmVariables = $variables;
644
645 1
        return $this;
646
    }
647
648
    /**
649
     * Get front matter variables.
650
     */
651 1
    public function getFmVariables(): array
652
    {
653 1
        return $this->fmVariables;
654
    }
655
656
    /**
657
     * {@inheritdoc}
658
     */
659 1
    public function setId(string $id): self
660
    {
661 1
        return parent::setId($id);
662
    }
663
664
    /**
665
     * Set parent page.
666
     */
667 1
    public function setParent(self $page): self
668
    {
669 1
        if ($page->getId() != $this->getId()) {
670 1
            $this->parent = $page;
671
        }
672
673 1
        return $this;
674
    }
675
676
    /**
677
     * Returns parent page if exists.
678
     */
679 1
    public function getParent(): ?self
680
    {
681 1
        return $this->parent;
682
    }
683
684
    /**
685
     * Cast "boolean" string (or array of strings) to boolean.
686
     *
687
     * @param mixed $value Value to filter
688
     *
689
     * @return bool|mixed
690
     *
691
     * @see strToBool()
692
     */
693 1
    private function filterBool(&$value)
694
    {
695 1
        \Cecil\Util\Str::strToBool($value);
696 1
        if (\is_array($value)) {
697 1
            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 1
    private static function getFileComponents(SplFileInfo $file): array
711
    {
712 1
        return [
713 1
            'path' => str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()),
714 1
            'name' => (string) str_ireplace('readme', 'index', $file->getBasename('.' . $file->getExtension())),
715 1
            'ext'  => $file->getExtension(),
716 1
        ];
717
    }
718
}
719