Passed
Push — fix/homepage ( 727ce5 )
by Arnaud
09:03 queued 04:10
created

Page::setFile()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 55
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 27
nc 16
nop 1
dl 0
loc 55
rs 8.8657
c 0
b 0
f 0

How to fix   Long Method   

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
    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), '/');
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
    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
        // case of "index"
131
        if ($fileName == 'index') {
132
            $this->setType(Type::HOMEPAGE);
133
        }
134
        /*
135
         * Set protected variables
136
         */
137
        $this->setFolder($fileRelativePath); // ie: "blog"
138
        $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

138
        $this->setSlug(/** @scrutinizer ignore-type */ $fileName); // ie: "post-1"
Loading history...
139
        $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"
140
        /*
141
         * Set default variables
142
         */
143
        $this->setVariables([
144
            '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

144
            'title'    => PrefixSuffix::sub(/** @scrutinizer ignore-type */ $fileName),
Loading history...
145
            'date'     => (new \DateTime())->setTimestamp($this->file->getCTime()),
146
            'updated'  => (new \DateTime())->setTimestamp($this->file->getMTime()),
147
            'filepath' => $this->file->getRelativePathname(),
148
        ]);
149
        /*
150
         * Set specific variables
151
         */
152
        // is file has a prefix?
153
        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

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

154
            $prefix = PrefixSuffix::getPrefix(/** @scrutinizer ignore-type */ $fileName);
Loading history...
155
            if ($prefix !== null) {
156
                // prefix is a valid date?
157
                if (Util\Date::isDateValid($prefix)) {
158
                    $this->setVariable('date', (string) $prefix);
159
                } else {
160
                    // prefix is an integer: used for sorting
161
                    $this->setVariable('weight', (int) $prefix);
162
                }
163
            }
164
        }
165
        // is file has a suffix?
166
        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

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

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