Passed
Pull Request — master (#1013)
by lee
07:38
created

Page::setVariable()   B

Complexity

Conditions 10
Paths 18

Size

Total Lines 45
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 14.4663

Importance

Changes 0
Metric Value
cc 10
eloc 33
c 0
b 0
f 0
nc 18
nop 2
dl 0
loc 45
ccs 20
cts 31
cp 0.6452
crap 14.4663
rs 7.6666

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
 * This file is part of the Cecil/Cecil package.
4
 *
5
 * Copyright (c) Arnaud Ligny <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Cecil\Collection\Page;
14
15
use Cecil\Collection\Item;
16
use Cecil\Config;
17
use Cecil\Util;
18
use Cocur\Slugify\Slugify;
19
use Symfony\Component\Finder\SplFileInfo;
20
21
/**
22
 * Class Page.
23
 */
24
class Page extends Item
25
{
26
    const SLUGIFY_PATTERN = '/(^\/|[^._a-z0-9\/]|-)+/'; // should be '/^\/|[^_a-z0-9\/]+/'
27
28
    /** @var bool True if page is not created from a Markdown file. */
29
    protected $virtual;
30
    /** @var SplFileInfo */
31
    protected $file;
32
    /** @var string Homepage, Page, Section, etc. */
33
    protected $type;
34
    /** @var string */
35
    protected $folder;
36
    /** @var string */
37
    protected $slug;
38
    /** @var string folder + slug */
39
    protected $path;
40
    /** @var string */
41
    protected $section;
42
    /** @var string */
43
    protected $frontmatter;
44
    /** @var string Body before conversion. */
45
    protected $body;
46
    /** @var array Front matter before conversion. */
47
    protected $fmVariables = [];
48
    /** @var string Body after Markdown conversion. */
49
    protected $html;
50
    /** @var Slugify */
51
    private static $slugifier;
52
53
    /**
54
     * @param string $id
55
     */
56 1
    public function __construct(string $id)
57
    {
58 1
        parent::__construct($id);
59 1
        $this->setVirtual(true);
60 1
        $this->setType(Type::PAGE);
61
        // default variables
62 1
        $this->setVariables([
63 1
            'title'            => 'Page Title',
64 1
            'date'             => new \DateTime(),
65 1
            'updated'          => new \DateTime(),
66
            'weight'           => null,
67
            'filepath'         => null,
68
            'published'        => true,
69 1
            'content_template' => 'page.content.twig',
70
        ]);
71 1
    }
72
73
    /**
74
     * Turns a path (string) into a slug (URI).
75
     *
76
     * @param string $path
77
     *
78
     * @return string
79
     */
80 1
    public static function slugify(string $path): string
81
    {
82 1
        if (!self::$slugifier instanceof Slugify) {
83 1
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
84
        }
85
86 1
        return self::$slugifier->slugify($path);
87
    }
88
89
    /**
90
     * Creates the ID from the file path.
91
     *
92
     * @param SplFileInfo $file
93
     *
94
     * @return string
95
     */
96 1
    public static function createId(SplFileInfo $file): string
97
    {
98 1
        $relativepath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
99 1
        $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension())));
100
        // case of "README" -> index
101 1
        $basename = str_ireplace('readme', 'index', $basename);
102
        // case of section's index: "section/index" -> "section"
103 1
        if (!empty($relativepath) && $basename == 'index') {
104 1
            return $relativepath;
105
        }
106
107 1
        return trim(Util::joinPath($relativepath, $basename), '/');
0 ignored issues
show
Bug introduced by
It seems like $basename can also be of type array; however, parameter $path of Cecil\Util::joinPath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

107
        return trim(Util::joinPath($relativepath, /** @scrutinizer ignore-type */ $basename), '/');
Loading history...
108
    }
109
110
    /**
111
     * Set file.
112
     *
113
     * @param SplFileInfo $file
114
     *
115
     * @return self
116
     */
117 1
    public function setFile(SplFileInfo $file): self
118
    {
119 1
        $this->setVirtual(false);
120 1
        $this->file = $file;
121
122
        /*
123
         * File path components
124
         */
125 1
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
126 1
        $fileExtension = $this->file->getExtension();
127 1
        $fileName = $this->file->getBasename('.'.$fileExtension);
128
        // case of "README" -> index
129 1
        $fileName = str_ireplace('readme', 'index', $fileName);
130
        /*
131
         * Set protected variables
132
         */
133 1
        $this->setFolder($fileRelativePath); // ie: "blog"
134 1
        $this->setSlug($fileName); // ie: "post-1"
0 ignored issues
show
Bug introduced by
It seems like $fileName can also be of type array; however, parameter $slug of Cecil\Collection\Page\Page::setSlug() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

134
        $this->setSlug(/** @scrutinizer ignore-type */ $fileName); // ie: "post-1"
Loading history...
135 1
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
136
        /*
137
         * Set default variables
138
         */
139 1
        $this->setVariables([
140 1
            'title'    => PrefixSuffix::sub($fileName),
0 ignored issues
show
Bug introduced by
It seems like $fileName can also be of type array; however, parameter $string of Cecil\Collection\Page\PrefixSuffix::sub() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

140
            'title'    => PrefixSuffix::sub(/** @scrutinizer ignore-type */ $fileName),
Loading history...
141 1
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
142 1
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
143 1
            'filepath' => $this->file->getRelativePathname(),
144
        ]);
145
        /*
146
         * Set specific variables
147
         */
148
        // is file has a prefix?
149 1
        if (PrefixSuffix::hasPrefix($fileName)) {
0 ignored issues
show
Bug introduced by
It seems like $fileName can also be of type array; however, parameter $string of Cecil\Collection\Page\PrefixSuffix::hasPrefix() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        if (PrefixSuffix::hasPrefix(/** @scrutinizer ignore-type */ $fileName)) {
Loading history...
150 1
            $prefix = PrefixSuffix::getPrefix($fileName);
0 ignored issues
show
Bug introduced by
It seems like $fileName can also be of type array; however, parameter $string of Cecil\Collection\Page\PrefixSuffix::getPrefix() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

150
            $prefix = PrefixSuffix::getPrefix(/** @scrutinizer ignore-type */ $fileName);
Loading history...
151 1
            if ($prefix !== null) {
152
                // prefix is a valid date?
153 1
                if (Util::isDateValid($prefix)) {
154 1
                    $this->setVariable('date', (string) $prefix);
155
                } else {
156
                    // prefix is an integer: used for sorting
157 1
                    $this->setVariable('weight', (int) $prefix);
158
                }
159
            }
160
        }
161
        // is file has a suffix?
162 1
        if (PrefixSuffix::hasSuffix($fileName)) {
0 ignored issues
show
Bug introduced by
It seems like $fileName can also be of type array; however, parameter $string of Cecil\Collection\Page\PrefixSuffix::hasSuffix() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
        if (PrefixSuffix::hasSuffix(/** @scrutinizer ignore-type */ $fileName)) {
Loading history...
163 1
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
0 ignored issues
show
Bug introduced by
It seems like $fileName can also be of type array; however, parameter $string of Cecil\Collection\Page\PrefixSuffix::getSuffix() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
            $this->setVariable('language', PrefixSuffix::getSuffix(/** @scrutinizer ignore-type */ $fileName));
Loading history...
164
        }
165 1
        $this->setVariable('langref', PrefixSuffix::sub($fileName));
166
167 1
        return $this;
168
    }
169
170
    /**
171
     * Parse file content.
172
     *
173
     * @return self
174
     */
175 1
    public function parse(): self
176
    {
177 1
        $parser = new Parser($this->file);
178 1
        $parsed = $parser->parse();
179 1
        $this->frontmatter = $parsed->getFrontmatter();
180 1
        $this->body = $parsed->getBody();
181
182 1
        return $this;
183
    }
184
185
    /**
186
     * Get frontmatter.
187
     *
188
     * @return string|null
189
     */
190 1
    public function getFrontmatter(): ?string
191
    {
192 1
        return $this->frontmatter;
193
    }
194
195
    /**
196
     * Get body as raw.
197
     *
198
     * @return string
199
     */
200 1
    public function getBody(): ?string
201
    {
202 1
        return $this->body;
203
    }
204
205
    /**
206
     * Set virtual status.
207
     *
208
     * @param bool $virtual
209
     *
210
     * @return self
211
     */
212 1
    public function setVirtual(bool $virtual): self
213
    {
214 1
        $this->virtual = $virtual;
215
216 1
        return $this;
217
    }
218
219
    /**
220
     * Is current page is virtual?
221
     *
222
     * @return bool
223
     */
224 1
    public function isVirtual(): bool
225
    {
226 1
        return $this->virtual;
227
    }
228
229
    /**
230
     * Set page type.
231
     *
232
     * @param string $type
233
     *
234
     * @return self
235
     */
236 1
    public function setType(string $type): self
237
    {
238 1
        $this->type = new Type($type);
239
240 1
        return $this;
241
    }
242
243
    /**
244
     * Get page type.
245
     *
246
     * @return string
247
     */
248 1
    public function getType(): string
249
    {
250 1
        return (string) $this->type;
251
    }
252
253
    /**
254
     * Set path without slug.
255
     *
256
     * @param string $folder
257
     *
258
     * @return self
259
     */
260 1
    public function setFolder(string $folder): self
261
    {
262 1
        $this->folder = self::slugify($folder);
263
264 1
        return $this;
265
    }
266
267
    /**
268
     * Get path without slug.
269
     *
270
     * @return string|null
271
     */
272 1
    public function getFolder(): ?string
273
    {
274 1
        return $this->folder;
275
    }
276
277
    /**
278
     * Set slug.
279
     *
280
     * @param string $slug
281
     *
282
     * @return self
283
     */
284 1
    public function setSlug(string $slug): self
285
    {
286 1
        if (!$this->slug) {
287 1
            $slug = self::slugify(PrefixSuffix::sub($slug));
288
        }
289
        // force slug and update path
290 1
        if ($this->slug && $this->slug != $slug) {
291 1
            $this->setPath($this->getFolder().'/'.$slug);
292
        }
293 1
        $this->slug = $slug;
294
295 1
        return $this;
296
    }
297
298
    /**
299
     * Get slug.
300
     *
301
     * @return string
302
     */
303 1
    public function getSlug(): string
304
    {
305 1
        return $this->slug;
306
    }
307
308
    /**
309
     * Set path.
310
     *
311
     * @param string $path
312
     *
313
     * @return self
314
     */
315 1
    public function setPath(string $path): self
316
    {
317 1
        $path = self::slugify(PrefixSuffix::sub($path));
318
319
        // case of homepage
320 1
        if ($path == 'index') {
321 1
            $this->path = '';
322
323 1
            return $this;
324
        }
325
326
        // case of custom sections' index (ie: content/section/index.md)
327 1
        if (substr($path, -6) == '/index') {
328 1
            $path = substr($path, 0, strlen($path) - 6);
329
        }
330 1
        $this->path = $path;
331
332
        // case of root pages
333 1
        $lastslash = strrpos($this->path, '/');
334 1
        if ($lastslash === false) {
335 1
            $this->slug = $this->path;
336
337 1
            return $this;
338
        }
339
340 1
        if (!$this->virtual && $this->getSection() === null) {
341 1
            $this->section = explode('/', $this->path)[0];
342
        }
343 1
        $this->folder = substr($this->path, 0, $lastslash);
344 1
        $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));
345
346 1
        return $this;
347
    }
348
349
    /**
350
     * Get path.
351
     *
352
     * @return string|null
353
     */
354 1
    public function getPath(): ?string
355
    {
356 1
        return $this->path;
357
    }
358
359
    /**
360
     * @see getPath()
361
     *
362
     * @return string|null
363
     */
364
    public function getPathname(): ?string
365
    {
366
        return $this->getPath();
367
    }
368
369
    /**
370
     * Set section.
371
     *
372
     * @param string $section
373
     *
374
     * @return self
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
     * @return string|null
387
     */
388 1
    public function getSection(): ?string
389
    {
390 1
        return !empty($this->section) ? $this->section : null;
391
    }
392
393
    /**
394
     * Set body as HTML.
395
     *
396
     * @param string $html
397
     *
398
     * @return self
399
     */
400 1
    public function setBodyHtml(string $html): self
401
    {
402 1
        $this->html = $html;
403
404 1
        return $this;
405
    }
406
407
    /**
408
     * Get body as HTML.
409
     *
410
     * @return string|null
411
     */
412 1
    public function getBodyHtml(): ?string
413
    {
414 1
        return $this->html;
415
    }
416
417
    /**
418
     * @see getBodyHtml()
419
     *
420
     * @return string|null
421
     */
422 1
    public function getContent(): ?string
423
    {
424 1
        return $this->getBodyHtml();
425
    }
426
427
    /**
428
     * Returns the path to the output (rendered) file.
429
     *
430
     * Use cases:
431
     * - default: path + suffix + extension (ie: blog/post-1/index.html)
432
     * - subpath: path + subpath + suffix + extension (ie: blog/post-1/amp/index.html)
433
     * - ugly: path + extension (ie: 404.html, sitemap.xml, robots.txt)
434
     * - path only (ie: _redirects)
435
     * - i18n: language + path + suffix + extension (ie: fr/blog/page/index.html)
436
     *
437
     * @param string      $format
438
     * @param Config|null $config
439
     *
440
     * @return string
441
     */
442 1
    public function getOutputFile(string $format, Config $config = null): string
443
    {
444 1
        $path = $this->getPath();
445 1
        $subpath = '';
446 1
        $suffix = '/index';
447 1
        $extension = 'html';
448 1
        $uglyurl = (bool) $this->getVariable('uglyurl');
449 1
        $language = $this->getVariable('language');
450
451
        // site config
452 1
        if ($config) {
453 1
            $subpath = (string) $config->getOutputFormatProperty($format, 'subpath');
454 1
            $suffix = (string) $config->getOutputFormatProperty($format, 'suffix');
455 1
            $extension = (string) $config->getOutputFormatProperty($format, 'extension');
456
        }
457
458
        // if ugly URL: not suffix
459 1
        if ($uglyurl) {
460 1
            $suffix = null;
461
        }
462
        // formatting strings
463 1
        if ($subpath) {
464
            $subpath = \sprintf('/%s', ltrim($subpath, '/'));
465
        }
466 1
        if ($suffix) {
467 1
            $suffix = \sprintf('/%s', ltrim($suffix, '/'));
468
        }
469 1
        if ($extension) {
470 1
            $extension = \sprintf('.%s', $extension);
471
        }
472 1
        if ($language !== null) {
473 1
            $language = \sprintf('%s/', $language);
474
        }
475
        // homepage special case: path = 'index'
476 1
        if (empty($path) && empty($suffix)) {
477 1
            $path = 'index';
478
        }
479
480 1
        return $language.$path.$subpath.$suffix.$extension;
481
    }
482
483
    /**
484
     * Returns the public URL.
485
     *
486
     * @param string      $format
487
     * @param Config|null $config
488
     *
489
     * @return string
490
     */
491 1
    public function getUrl(string $format = 'html', Config $config = null): string
492
    {
493 1
        $uglyurl = $this->getVariable('uglyurl') ? true : false;
494 1
        $output = $this->getOutputFile($format, $config);
495
496 1
        if (!$uglyurl) {
497 1
            $output = str_replace('index.html', '', $output);
498
        }
499
500 1
        return $output;
501
    }
502
503
    /*
504
     * Helpers to set and get variables.
505
     */
506
507
    /**
508
     * Set an array as variables.
509
     *
510
     * @param array $variables
511
     *
512
     * @throws \Exception
513
     *
514
     * @return self
515
     */
516 1
    public function setVariables(array $variables): self
517
    {
518 1
        foreach ($variables as $key => $value) {
519 1
            $this->setVariable($key, $value);
520
        }
521
522 1
        return $this;
523
    }
524
525
    /**
526
     * Get all variables.
527
     *
528
     * @return array
529
     */
530 1
    public function getVariables(): array
531
    {
532 1
        return $this->properties;
533
    }
534
535
    /**
536
     * Set a variable.
537
     *
538
     * @param string $name
539
     * @param mixed  $value
540
     *
541
     * @throws \Exception
542
     *
543
     * @return self
544
     */
545 1
    public function setVariable(string $name, $value): self
546
    {
547 1
        if (is_bool($value)) {
548 1
            $value = $value ?: 0;
549
        }
550
        switch ($name) {
551 1
            case 'date':
552
                try {
553 1
                    $date = Util::dateToDatetime($value);
554
                } catch (\Exception $e) {
555
                    throw new \Exception(sprintf(
556
                        'Expected date format (ie: "2012-10-08") for "date" in "%s" instead of "%s"',
557
                        $this->getId(),
558
                        (string) $value
559
                    ));
560
                }
561 1
                $this->offsetSet('date', $date);
562 1
                break;
563 1
            case 'draft':
564 1
                if ($value === true) {
565 1
                    $this->offsetSet('published', false);
566
                }
567 1
                break;
568 1
            case 'path':
569 1
            case 'slug':
570 1
                $slugify = self::slugify((string) $value);
571 1
                if ($value != $slugify) {
572
                    throw new \Exception(sprintf(
573
                        '"%s" variable should be "%s" (not "%s") in "%s"',
574
                        $name,
575
                        $slugify,
576
                        (string) $value,
577
                        $this->getId()
578
                    ));
579
                }
580
                /** @see setPath() */
581
                /** @see setSlug() */
582 1
                $method = 'set'.\ucfirst($name);
583 1
                $this->$method($value);
584 1
                break;
585
            default:
586 1
                $this->offsetSet($name, $value);
587
        }
588
589 1
        return $this;
590
    }
591
592
    /**
593
     * Is variable exists?
594
     *
595
     * @param string $name
596
     *
597
     * @return bool
598
     */
599 1
    public function hasVariable(string $name): bool
600
    {
601 1
        return $this->offsetExists($name);
602
    }
603
604
    /**
605
     * Get a variable.
606
     *
607
     * @param string $name
608
     *
609
     * @return mixed|null
610
     */
611 1
    public function getVariable(string $name)
612
    {
613 1
        if ($this->offsetExists($name)) {
614 1
            return $this->offsetGet($name);
615
        }
616 1
    }
617
618
    /**
619
     * Unset a variable.
620
     *
621
     * @param string $name
622
     *
623
     * @return self
624
     */
625 1
    public function unVariable(string $name): self
626
    {
627 1
        if ($this->offsetExists($name)) {
628 1
            $this->offsetUnset($name);
629
        }
630
631 1
        return $this;
632
    }
633
634
    /**
635
     * Set front matter (only) variables.
636
     *
637
     * @param array $variables
638
     *
639
     * @return self
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
     * @return array
652
     */
653 1
    public function getFmVariables(): array
654
    {
655 1
        return $this->fmVariables;
656
    }
657
}
658