Passed
Push — 5 ( 9f686a...328525 )
by
unknown
03:31
created

Page   F

Complexity

Total Complexity 65

Size/Duplication

Total Lines 502
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 159
c 1
b 1
f 0
dl 0
loc 502
rs 3.2
wmc 65

33 Methods

Rating   Name   Duplication   Size   Complexity  
A getBody() 0 3 1
B setFile() 0 56 7
A getIdWithoutLang() 0 3 1
A slugify() 0 7 2
A __construct() 0 14 1
A createId() 0 17 4
A isVirtual() 0 3 1
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
A parse() 0 8 1
A hasVariable() 0 3 1
A setType() 0 5 1
A setFmVariables() 0 5 1
A setSection() 0 5 1
A getVariables() 0 3 1
A setPath() 0 32 6
A unVariable() 0 7 2
A getSection() 0 3 2
B setVariable() 0 45 10
A getFilePath() 0 3 2
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\Util;
17
use Cocur\Slugify\Slugify;
18
use Symfony\Component\Finder\SplFileInfo;
19
20
/**
21
 * Class Page.
22
 */
23
class Page extends Item
24
{
25
    const SLUGIFY_PATTERN = '/(^\/|[^._a-z0-9\/]|-)+/'; // should be '/^\/|[^_a-z0-9\/]+/'
26
27
    /** @var bool True if page is not created from a Markdown file. */
28
    protected $virtual;
29
30
    /** @var SplFileInfo */
31
    protected $file;
32
33
    /** @var string Homepage, Page, Section, etc. */
34
    protected $type;
35
36
    /** @var string */
37
    protected $folder;
38
39
    /** @var string */
40
    protected $slug;
41
42
    /** @var string folder + slug. */
43
    protected $path;
44
45
    /** @var string */
46
    protected $section;
47
48
    /** @var string */
49
    protected $frontmatter;
50
51
    /** @var string Body before conversion. */
52
    protected $body;
53
54
    /** @var array Front matter before conversion. */
55
    protected $fmVariables = [];
56
57
    /** @var string Body after Markdown conversion. */
58
    protected $html;
59
60
    /** @var Slugify */
61
    private static $slugifier;
62
63
    public function __construct(string $id)
64
    {
65
        parent::__construct($id);
66
        $this->setVirtual(true);
67
        $this->setType(Type::PAGE);
68
        // default variables
69
        $this->setVariables([
70
            'title'            => 'Page Title',
71
            'date'             => new \DateTime(),
72
            'updated'          => new \DateTime(),
73
            'weight'           => null,
74
            'filepath'         => null,
75
            'published'        => true,
76
            'content_template' => 'page.content.twig',
77
        ]);
78
    }
79
80
    /**
81
     * Turns a path (string) into a slug (URI).
82
     */
83
    public static function slugify(string $path): string
84
    {
85
        if (!self::$slugifier instanceof Slugify) {
86
            self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);
87
        }
88
89
        return self::$slugifier->slugify($path);
90
    }
91
92
    /**
93
     * Creates the ID from the file path.
94
     */
95
    public static function createId(SplFileInfo $file): string
96
    {
97
        $relativepath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
98
        $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension())));
99
        // case of "README" -> index
100
        $basename = (string) str_ireplace('readme', 'index', $basename);
101
        // case of section's index: "section/index" -> "section"
102
        if (!empty($relativepath) && PrefixSuffix::sub($basename) == 'index') {
103
            // case of a localized section
104
            if (PrefixSuffix::hasSuffix($basename)) {
105
                return $relativepath.'.'.PrefixSuffix::getSuffix($basename);
106
            }
107
108
            return $relativepath;
109
        }
110
111
        return trim(Util::joinPath($relativepath, $basename), '/');
112
    }
113
114
    /**
115
     * Returns the ID of a page without language suffix.
116
     */
117
    public function getIdWithoutLang(): string
118
    {
119
        return PrefixSuffix::sub($this->getId());
120
    }
121
122
    /**
123
     * Set file.
124
     */
125
    public function setFile(SplFileInfo $file): self
126
    {
127
        $this->setVirtual(false);
128
        $this->file = $file;
129
130
        /*
131
         * File path components
132
         */
133
        $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
134
        $fileExtension = $this->file->getExtension();
135
        $fileName = $this->file->getBasename('.'.$fileExtension);
136
        // case of "README" -> "index"
137
        $fileName = (string) str_ireplace('readme', 'index', $fileName);
138
        // case of "index" = home page
139
        if (empty($this->file->getRelativePath()) && PrefixSuffix::sub($fileName) == 'index') {
140
            $this->setType(Type::HOMEPAGE);
141
        }
142
        /*
143
         * Set protected variables
144
         */
145
        $this->setFolder($fileRelativePath); // ie: "blog"
146
        $this->setSlug($fileName); // ie: "post-1"
147
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
148
        /*
149
         * Set default variables
150
         */
151
        $this->setVariables([
152
            'title'    => PrefixSuffix::sub($fileName),
153
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
154
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
155
            'filepath' => $this->file->getRelativePathname(),
156
        ]);
157
        /*
158
         * Set specific variables
159
         */
160
        // is file has a prefix?
161
        if (PrefixSuffix::hasPrefix($fileName)) {
162
            $prefix = PrefixSuffix::getPrefix($fileName);
163
            if ($prefix !== null) {
164
                // prefix is a valid date?
165
                if (Util\Date::isDateValid($prefix)) {
166
                    $this->setVariable('date', (string) $prefix);
167
                } else {
168
                    // prefix is an integer: used for sorting
169
                    $this->setVariable('weight', (int) $prefix);
170
                }
171
            }
172
        }
173
        // is file has a language suffix?
174
        if (PrefixSuffix::hasSuffix($fileName)) {
175
            $this->setVariable('language', PrefixSuffix::getSuffix($fileName));
176
        }
177
        // set reference between translations
178
        $this->setVariable('langref', $this->getPath());
179
180
        return $this;
181
    }
182
183
    /**
184
     * Returns file real path.
185
     */
186
    public function getFilePath(): ?string
187
    {
188
        return $this->file->getRealPath() === false ? null : $this->file->getRealPath();
189
    }
190
191
    /**
192
     * Parse file content.
193
     */
194
    public function parse(): self
195
    {
196
        $parser = new Parser($this->file);
197
        $parsed = $parser->parse();
198
        $this->frontmatter = $parsed->getFrontmatter();
199
        $this->body = $parsed->getBody();
200
201
        return $this;
202
    }
203
204
    /**
205
     * Get frontmatter.
206
     */
207
    public function getFrontmatter(): ?string
208
    {
209
        return $this->frontmatter;
210
    }
211
212
    /**
213
     * Get body as raw.
214
     */
215
    public function getBody(): ?string
216
    {
217
        return $this->body;
218
    }
219
220
    /**
221
     * Set virtual status.
222
     */
223
    public function setVirtual(bool $virtual): self
224
    {
225
        $this->virtual = $virtual;
226
227
        return $this;
228
    }
229
230
    /**
231
     * Is current page is virtual?
232
     */
233
    public function isVirtual(): bool
234
    {
235
        return $this->virtual;
236
    }
237
238
    /**
239
     * Set page type.
240
     */
241
    public function setType(string $type): self
242
    {
243
        $this->type = new Type($type);
244
245
        return $this;
246
    }
247
248
    /**
249
     * Get page type.
250
     */
251
    public function getType(): string
252
    {
253
        return (string) $this->type;
254
    }
255
256
    /**
257
     * Set path without slug.
258
     */
259
    public function setFolder(string $folder): self
260
    {
261
        $this->folder = self::slugify($folder);
262
263
        return $this;
264
    }
265
266
    /**
267
     * Get path without slug.
268
     */
269
    public function getFolder(): ?string
270
    {
271
        return $this->folder;
272
    }
273
274
    /**
275
     * Set slug.
276
     */
277
    public function setSlug(string $slug): self
278
    {
279
        if (!$this->slug) {
280
            $slug = self::slugify(PrefixSuffix::sub($slug));
281
        }
282
        // force slug and update path
283
        if ($this->slug && $this->slug != $slug) {
284
            $this->setPath($this->getFolder().'/'.$slug);
285
        }
286
        $this->slug = $slug;
287
288
        return $this;
289
    }
290
291
    /**
292
     * Get slug.
293
     */
294
    public function getSlug(): string
295
    {
296
        return $this->slug;
297
    }
298
299
    /**
300
     * Set path.
301
     */
302
    public function setPath(string $path): self
303
    {
304
        $path = self::slugify(PrefixSuffix::sub($path));
305
306
        // case of homepage
307
        if ($path == 'index') {
308
            $this->path = '';
309
310
            return $this;
311
        }
312
313
        // case of custom sections' index (ie: content/section/index.md)
314
        if (substr($path, -6) == '/index') {
315
            $path = substr($path, 0, strlen($path) - 6);
316
        }
317
        $this->path = $path;
318
319
        // case of root pages
320
        $lastslash = strrpos($this->path, '/');
321
        if ($lastslash === false) {
322
            $this->slug = $this->path;
323
324
            return $this;
325
        }
326
327
        if (!$this->virtual && $this->getSection() === null) {
328
            $this->section = explode('/', $this->path)[0];
329
        }
330
        $this->folder = substr($this->path, 0, $lastslash);
331
        $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));
332
333
        return $this;
334
    }
335
336
    /**
337
     * Get path.
338
     */
339
    public function getPath(): ?string
340
    {
341
        return $this->path;
342
    }
343
344
    /**
345
     * @see getPath()
346
     */
347
    public function getPathname(): ?string
348
    {
349
        return $this->getPath();
350
    }
351
352
    /**
353
     * Set section.
354
     */
355
    public function setSection(string $section): self
356
    {
357
        $this->section = $section;
358
359
        return $this;
360
    }
361
362
    /**
363
     * Get section.
364
     */
365
    public function getSection(): ?string
366
    {
367
        return !empty($this->section) ? $this->section : null;
368
    }
369
370
    /**
371
     * Set body as HTML.
372
     */
373
    public function setBodyHtml(string $html): self
374
    {
375
        $this->html = $html;
376
377
        return $this;
378
    }
379
380
    /**
381
     * Get body as HTML.
382
     */
383
    public function getBodyHtml(): ?string
384
    {
385
        return $this->html;
386
    }
387
388
    /**
389
     * @see getBodyHtml()
390
     */
391
    public function getContent(): ?string
392
    {
393
        return $this->getBodyHtml();
394
    }
395
396
    /*
397
     * Helpers to set and get variables.
398
     */
399
400
    /**
401
     * Set an array as variables.
402
     *
403
     * @throws \Exception
404
     */
405
    public function setVariables(array $variables): self
406
    {
407
        foreach ($variables as $key => $value) {
408
            $this->setVariable($key, $value);
409
        }
410
411
        return $this;
412
    }
413
414
    /**
415
     * Get all variables.
416
     */
417
    public function getVariables(): array
418
    {
419
        return $this->properties;
420
    }
421
422
    /**
423
     * Set a variable.
424
     *
425
     * @param string $name
426
     * @param mixed  $value
427
     *
428
     * @throws \Exception
429
     */
430
    public function setVariable(string $name, $value): self
431
    {
432
        if (is_bool($value)) {
433
            $value = $value ?: 0;
434
        }
435
        switch ($name) {
436
            case 'date':
437
                try {
438
                    $date = Util\Date::dateToDatetime($value);
439
                } catch (\Exception $e) {
440
                    throw new \Exception(sprintf(
441
                        'Expected date format (ie: "2012-10-08") for "date" in "%s" instead of "%s"',
442
                        $this->getId(),
443
                        (string) $value
444
                    ));
445
                }
446
                $this->offsetSet('date', $date);
447
                break;
448
            case 'draft':
449
                if ($value === true) {
450
                    $this->offsetSet('published', false);
451
                }
452
                break;
453
            case 'path':
454
            case 'slug':
455
                $slugify = self::slugify((string) $value);
456
                if ($value != $slugify) {
457
                    throw new \Exception(sprintf(
458
                        '"%s" variable should be "%s" (not "%s") in "%s"',
459
                        $name,
460
                        $slugify,
461
                        (string) $value,
462
                        $this->getId()
463
                    ));
464
                }
465
                /** @see setPath() */
466
                /** @see setSlug() */
467
                $method = 'set'.\ucfirst($name);
468
                $this->$method($value);
469
                break;
470
            default:
471
                $this->offsetSet($name, $value);
472
        }
473
474
        return $this;
475
    }
476
477
    /**
478
     * Is variable exists?
479
     */
480
    public function hasVariable(string $name): bool
481
    {
482
        return $this->offsetExists($name);
483
    }
484
485
    /**
486
     * Get a variable.
487
     *
488
     * @return mixed|null
489
     */
490
    public function getVariable(string $name)
491
    {
492
        if ($this->offsetExists($name)) {
493
            return $this->offsetGet($name);
494
        }
495
    }
496
497
    /**
498
     * Unset a variable.
499
     */
500
    public function unVariable(string $name): self
501
    {
502
        if ($this->offsetExists($name)) {
503
            $this->offsetUnset($name);
504
        }
505
506
        return $this;
507
    }
508
509
    /**
510
     * Set front matter (only) variables.
511
     */
512
    public function setFmVariables(array $variables): self
513
    {
514
        $this->fmVariables = $variables;
515
516
        return $this;
517
    }
518
519
    /**
520
     * Get front matter variables.
521
     */
522
    public function getFmVariables(): array
523
    {
524
        return $this->fmVariables;
525
    }
526
}
527