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