Passed
Push — feat/cast-bool ( 99e55e )
by Arnaud
05:35
created

Page::filterBool()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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