Passed
Push — menu ( 2edc93...bd9bd5 )
by Arnaud
14:43 queued 11:06
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
     * Returns the Id of a page withour language suffix.
112
     *
113
     * @return string
114
     */
115 1
    public function getIdWithoutLang(): string
116
    {
117 1
        return PrefixSuffix::sub($this->getId());
118
    }
119
120
    /**
121
     * Set file.
122
     *
123
     * @param SplFileInfo $file
124
     *
125
     * @return self
126
     */
127 1
    public function setFile(SplFileInfo $file): self
128
    {
129 1
        $this->setVirtual(false);
130 1
        $this->file = $file;
131
132
        /*
133
         * File path components
134
         */
135 1
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
136 1
        $fileExtension = $this->file->getExtension();
137 1
        $fileName = $this->file->getBasename('.'.$fileExtension);
138
        // case of "README" -> "index"
139 1
        $fileName = str_ireplace('readme', 'index', $fileName);
140
        // case of "index" = home page
141 1
        if (empty($this->file->getRelativePath()) && PrefixSuffix::sub($fileName) == 'index') {
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

141
        if (empty($this->file->getRelativePath()) && PrefixSuffix::sub(/** @scrutinizer ignore-type */ $fileName) == 'index') {
Loading history...
142 1
            $this->setType(Type::HOMEPAGE);
143
        }
144
        /*
145
         * Set protected variables
146
         */
147 1
        $this->setFolder($fileRelativePath); // ie: "blog"
148 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

148
        $this->setSlug(/** @scrutinizer ignore-type */ $fileName); // ie: "post-1"
Loading history...
149 1
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
150
        /*
151
         * Set default variables
152
         */
153 1
        $this->setVariables([
154 1
            'title'    => PrefixSuffix::sub($fileName),
155 1
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
156 1
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
157 1
            'filepath' => $this->file->getRelativePathname(),
158
        ]);
159
        /*
160
         * Set specific variables
161
         */
162
        // is file has a prefix?
163 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

163
        if (PrefixSuffix::hasPrefix(/** @scrutinizer ignore-type */ $fileName)) {
Loading history...
164 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

164
            $prefix = PrefixSuffix::getPrefix(/** @scrutinizer ignore-type */ $fileName);
Loading history...
165 1
            if ($prefix !== null) {
166
                // prefix is a valid date?
167 1
                if (Util\Date::isDateValid($prefix)) {
168 1
                    $this->setVariable('date', (string) $prefix);
169
                } else {
170
                    // prefix is an integer: used for sorting
171 1
                    $this->setVariable('weight', (int) $prefix);
172
                }
173
            }
174
        }
175
        // is file has a language suffix?
176 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

176
        if (PrefixSuffix::hasSuffix(/** @scrutinizer ignore-type */ $fileName)) {
Loading history...
177 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

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