Passed
Push — front-matter-variables ( c313b6 )
by Arnaud
03:54
created

Page   F

Complexity

Total Complexity 71

Size/Duplication

Total Lines 632
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 181
dl 0
loc 632
rs 2.7199
c 3
b 0
f 0
wmc 71

33 Methods

Rating   Name   Duplication   Size   Complexity  
A isVirtual() 0 3 1
A setFile() 0 51 5
A getType() 0 3 1
A getPath() 0 3 1
A setBodyHtml() 0 5 1
A getBodyHtml() 0 3 1
A getFrontmatter() 0 3 1
A getFolder() 0 3 1
A setVirtual() 0 5 1
A setFolder() 0 5 1
A getFmVariables() 0 3 1
A setVariables() 0 7 2
A getVariable() 0 4 2
B getOutputFile() 0 39 9
A parse() 0 8 1
A createId() 0 12 3
A hasVariable() 0 3 1
A setType() 0 5 1
A getBody() 0 3 1
A setFmVariables() 0 5 1
A slugify() 0 7 2
A setSection() 0 5 1
A __construct() 0 14 1
A getVariables() 0 3 1
A getUrl() 0 10 3
A setPath() 0 32 6
A unVariable() 0 7 2
A getSection() 0 3 2
B setVariable() 0 45 10
A getSlug() 0 3 1
A setSlug() 0 12 4
A getContent() 0 3 1
A getPathname() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Page often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Page, and based on these observations, apply Extract Interface, too.

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 variables. */
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
    public function __construct(string $id)
57
    {
58
        parent::__construct($id);
59
        $this->setVirtual(true);
60
        $this->setType(Type::PAGE);
61
        // default variables
62
        $this->setVariables([
63
            'title'            => 'Page Title',
64
            'date'             => new \DateTime(),
65
            'updated'          => new \DateTime(),
66
            'weight'           => null,
67
            'filepath'         => null,
68
            'published'        => true,
69
            'content_template' => 'page.content.twig',
70
        ]);
71
    }
72
73
    /**
74
     * Turns a path (string) into a slug (URI).
75
     *
76
     * @param string $path
77
     *
78
     * @return string
79
     */
80
    public static function slugify(string $path): string
81
    {
82
        if (!self::$slugifier instanceof Slugify) {
83
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
84
        }
85
86
        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
    public static function createId(SplFileInfo $file): string
97
    {
98
        $relativepath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
99
        $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension())));
100
        // case of "README" -> index
101
        $basename = str_ireplace('readme', 'index', $basename);
102
        // case of section's index: "section/index" -> "section"
103
        if (!empty($relativepath) && $basename == 'index') {
104
            return $relativepath;
105
        }
106
107
        return trim(Util::joinPath($relativepath, $basename), '/');
108
    }
109
110
    /**
111
     * Set file.
112
     *
113
     * @param SplFileInfo $file
114
     *
115
     * @return self
116
     */
117
    public function setFile(SplFileInfo $file): self
118
    {
119
        $this->setVirtual(false);
120
        $this->file = $file;
121
122
        /*
123
         * File path components
124
         */
125
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
126
        $fileExtension = $this->file->getExtension();
127
        $fileName = $this->file->getBasename('.'.$fileExtension);
128
        // case of "README" -> index
129
        $fileName = str_ireplace('readme', 'index', $fileName);
130
        /*
131
         * Set protected variables
132
         */
133
        $this->setFolder($fileRelativePath); // ie: "blog"
134
        $this->setSlug($fileName); // ie: "post-1"
135
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
136
        /*
137
         * Set default variables
138
         */
139
        $this->setVariables([
140
            'title'    => PrefixSuffix::sub($fileName),
141
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
142
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
143
            'filepath' => $this->file->getRelativePathname(),
144
        ]);
145
        /*
146
         * Set specific variables
147
         */
148
        // is file has a prefix?
149
        if (PrefixSuffix::hasPrefix($fileName)) {
150
            $prefix = PrefixSuffix::getPrefix($fileName);
151
            if ($prefix !== null) {
152
                // prefix is a valid date?
153
                if (Util::isDateValid($prefix)) {
154
                    $this->setVariable('date', (string) $prefix);
155
                } else {
156
                    // prefix is an integer: used for sorting
157
                    $this->setVariable('weight', (int) $prefix);
158
                }
159
            }
160
        }
161
        // is file has a suffix?
162
        if (PrefixSuffix::hasSuffix($fileName)) {
163
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
164
        }
165
        $this->setVariable('langref', PrefixSuffix::sub($fileName));
166
167
        return $this;
168
    }
169
170
    /**
171
     * Parse file content.
172
     *
173
     * @return self
174
     */
175
    public function parse(): self
176
    {
177
        $parser = new Parser($this->file);
178
        $parsed = $parser->parse();
179
        $this->frontmatter = $parsed->getFrontmatter();
180
        $this->body = $parsed->getBody();
181
182
        return $this;
183
    }
184
185
    /**
186
     * Get frontmatter.
187
     *
188
     * @return string|null
189
     */
190
    public function getFrontmatter(): ?string
191
    {
192
        return $this->frontmatter;
193
    }
194
195
    /**
196
     * Get body as raw.
197
     *
198
     * @return string
199
     */
200
    public function getBody(): ?string
201
    {
202
        return $this->body;
203
    }
204
205
    /**
206
     * Set virtual status.
207
     *
208
     * @param bool $virtual
209
     *
210
     * @return self
211
     */
212
    public function setVirtual(bool $virtual): self
213
    {
214
        $this->virtual = $virtual;
215
216
        return $this;
217
    }
218
219
    /**
220
     * Is current page is virtual?
221
     *
222
     * @return bool
223
     */
224
    public function isVirtual(): bool
225
    {
226
        return $this->virtual;
227
    }
228
229
    /**
230
     * Set page type.
231
     *
232
     * @param string $type
233
     *
234
     * @return self
235
     */
236
    public function setType(string $type): self
237
    {
238
        $this->type = new Type($type);
239
240
        return $this;
241
    }
242
243
    /**
244
     * Get page type.
245
     *
246
     * @return string
247
     */
248
    public function getType(): string
249
    {
250
        return (string) $this->type;
251
    }
252
253
    /**
254
     * Set path without slug.
255
     *
256
     * @param string $folder
257
     *
258
     * @return self
259
     */
260
    public function setFolder(string $folder): self
261
    {
262
        $this->folder = self::slugify($folder);
263
264
        return $this;
265
    }
266
267
    /**
268
     * Get path without slug.
269
     *
270
     * @return string|null
271
     */
272
    public function getFolder(): ?string
273
    {
274
        return $this->folder;
275
    }
276
277
    /**
278
     * Set slug.
279
     *
280
     * @param string $slug
281
     *
282
     * @return self
283
     */
284
    public function setSlug(string $slug): self
285
    {
286
        if (!$this->slug) {
287
            $slug = self::slugify(PrefixSuffix::sub($slug));
288
        }
289
        // force slug and update path
290
        if ($this->slug && $this->slug != $slug) {
291
            $this->setPath($this->getFolder().'/'.$slug);
292
        }
293
        $this->slug = $slug;
294
295
        return $this;
296
    }
297
298
    /**
299
     * Get slug.
300
     *
301
     * @return string
302
     */
303
    public function getSlug(): string
304
    {
305
        return $this->slug;
306
    }
307
308
    /**
309
     * Set path.
310
     *
311
     * @param string $path
312
     *
313
     * @return self
314
     */
315
    public function setPath(string $path): self
316
    {
317
        $path = self::slugify(PrefixSuffix::sub($path));
318
319
        // case of homepage
320
        if ($path == 'index') {
321
            $this->path = '';
322
323
            return $this;
324
        }
325
326
        // case of custom sections' index (ie: content/section/index.md)
327
        if (substr($path, -6) == '/index') {
328
            $path = substr($path, 0, strlen($path) - 6);
329
        }
330
        $this->path = $path;
331
332
        // case of root pages
333
        $lastslash = strrpos($this->path, '/');
334
        if ($lastslash === false) {
335
            $this->slug = $this->path;
336
337
            return $this;
338
        }
339
340
        if (!$this->virtual && $this->getSection() === null) {
341
            $this->section = explode('/', $this->path)[0];
342
        }
343
        $this->folder = substr($this->path, 0, $lastslash);
344
        $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));
345
346
        return $this;
347
    }
348
349
    /**
350
     * Get path.
351
     *
352
     * @return string|null
353
     */
354
    public function getPath(): ?string
355
    {
356
        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
    public function setSection(string $section): self
377
    {
378
        $this->section = $section;
379
380
        return $this;
381
    }
382
383
    /**
384
     * Get section.
385
     *
386
     * @return string|null
387
     */
388
    public function getSection(): ?string
389
    {
390
        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
    public function setBodyHtml(string $html): self
401
    {
402
        $this->html = $html;
403
404
        return $this;
405
    }
406
407
    /**
408
     * Get body as HTML.
409
     *
410
     * @return string|null
411
     */
412
    public function getBodyHtml(): ?string
413
    {
414
        return $this->html;
415
    }
416
417
    /**
418
     * @see getBodyHtml()
419
     *
420
     * @return string|null
421
     */
422
    public function getContent(): ?string
423
    {
424
        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
    public function getOutputFile(string $format, Config $config = null): string
443
    {
444
        $path = $this->getPath();
445
        $subpath = '';
446
        $suffix = '/index';
447
        $extension = 'html';
448
        $uglyurl = (bool) $this->getVariable('uglyurl');
449
        $language = $this->getVariable('language');
450
451
        // site config
452
        if ($config) {
453
            $subpath = (string) $config->getOutputFormatProperty($format, 'subpath');
454
            $suffix = (string) $config->getOutputFormatProperty($format, 'suffix');
455
            $extension = (string) $config->getOutputFormatProperty($format, 'extension');
456
        }
457
458
        // if ugly URL: not suffix
459
        if ($uglyurl) {
460
            $suffix = null;
461
        }
462
        // formatting strings
463
        if ($subpath) {
464
            $subpath = \sprintf('/%s', ltrim($subpath, '/'));
465
        }
466
        if ($suffix) {
467
            $suffix = \sprintf('/%s', ltrim($suffix, '/'));
468
        }
469
        if ($extension) {
470
            $extension = \sprintf('.%s', $extension);
471
        }
472
        if ($language !== null) {
473
            $language = \sprintf('%s/', $language);
474
        }
475
        // homepage special case: path = 'index'
476
        if (empty($path) && empty($suffix)) {
477
            $path = 'index';
478
        }
479
480
        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
    public function getUrl(string $format = 'html', Config $config = null): string
492
    {
493
        $uglyurl = $this->getVariable('uglyurl') ? true : false;
494
        $output = $this->getOutputFile($format, $config);
495
496
        if (!$uglyurl) {
497
            $output = str_replace('index.html', '', $output);
498
        }
499
500
        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
    public function setVariables(array $variables): self
517
    {
518
        foreach ($variables as $key => $value) {
519
            $this->setVariable($key, $value);
520
        }
521
522
        return $this;
523
    }
524
525
    /**
526
     * Get all variables.
527
     *
528
     * @return array
529
     */
530
    public function getVariables(): array
531
    {
532
        return $this->properties;
533
    }
534
535
    /**
536
     * Set a variable.
537
     *
538
     * @param $name
539
     * @param $value
540
     *
541
     * @throws \Exception
542
     *
543
     * @return self
544
     */
545
    public function setVariable($name, $value): self
546
    {
547
        if (is_bool($value)) {
548
            $value = $value ?: 0;
549
        }
550
        switch ($name) {
551
            case 'date':
552
                try {
553
                    $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
                        $value
559
                    ));
560
                }
561
                $this->offsetSet('date', $date);
562
                break;
563
            case 'draft':
564
                if ($value === true) {
565
                    $this->offsetSet('published', false);
566
                }
567
                break;
568
            case 'path':
569
            case 'slug':
570
                $slugify = self::slugify($value);
571
                if ($value != $slugify) {
572
                    throw new \Exception(sprintf(
573
                        '"%s" variable should be "%s" (not "%s") in "%s"',
574
                        $name,
575
                        $slugify,
576
                        $value,
577
                        $this->getId()
578
                    ));
579
                }
580
                /** @see setPath() */
581
                /** @see setSlug() */
582
                $method = 'set'.\ucfirst($name);
583
                $this->$method($value);
584
                break;
585
            default:
586
                $this->offsetSet($name, $value);
587
        }
588
589
        return $this;
590
    }
591
592
    /**
593
     * Is variable exists?
594
     *
595
     * @param string $name
596
     *
597
     * @return bool
598
     */
599
    public function hasVariable(string $name): bool
600
    {
601
        return $this->offsetExists($name);
602
    }
603
604
    /**
605
     * Get a variable.
606
     *
607
     * @param string $name
608
     *
609
     * @return mixed|null
610
     */
611
    public function getVariable(string $name)
612
    {
613
        if ($this->offsetExists($name)) {
614
            return $this->offsetGet($name);
615
        }
616
    }
617
618
    /**
619
     * Unset a variable.
620
     *
621
     * @param string $name
622
     *
623
     * @return self
624
     */
625
    public function unVariable(string $name): self
626
    {
627
        if ($this->offsetExists($name)) {
628
            $this->offsetUnset($name);
629
        }
630
631
        return $this;
632
    }
633
634
    /**
635
     * Set front matter (only) variables.
636
     *
637
     * @param array $variables
638
     *
639
     * @return self
640
     */
641
    public function setFmVariables(array $variables): self
642
    {
643
        $this->fmVariables = $variables;
644
645
        return $this;
646
    }
647
648
    /**
649
     * Get front matter variables.
650
     *
651
     * @return array
652
     */
653
    public function getFmVariables(): array
654
    {
655
        return $this->fmVariables;
656
    }
657
}
658