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