Cancelled
Pull Request — master (#1676)
by Arnaud
09:32 queued 03:17
created

Config::valid()   B

Complexity

Conditions 10
Paths 15

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 18.7765

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 17
c 2
b 0
f 0
nc 15
nop 0
dl 0
loc 31
ccs 10
cts 18
cp 0.5556
crap 18.7765
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
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Cecil;
15
16
use Cecil\Exception\ConfigException;
17
use Cecil\Exception\RuntimeException;
18
use Cecil\Util\Plateform;
19
use Dflydev\DotAccessData\Data;
20
21
/**
22
 * Class Config.
23
 */
24
class Config
25
{
26
    /** @var Data Configuration is a Data object. */
27
    protected $data;
28
29
    /** @var array Configuration. */
30
    protected $siteConfig;
31
32
    /** @var string Source directory. */
33
    protected $sourceDir;
34
35
    /** @var string Destination directory. */
36
    protected $destinationDir;
37
38
    /** @var array Languages. */
39
    protected $languages = null;
40
41
    public const LANG_CODE_PATTERN = '([a-z]{2}(-[A-Z]{2})?)'; // "fr" or "fr-FR"
42
    public const LANG_LOCALE_PATTERN = '[a-z]{2}(_[A-Z]{2})?(_[A-Z]{2})?'; // "fr" or "fr_FR" or "no_NO_NY"
43
44
    /**
45
     * Build the Config object with the default config + the optional given array.
46
     */
47 1
    public function __construct(?array $config = null)
48
    {
49
        // load default configuration
50 1
        $defaultConfig = realpath(Util::joinFile(__DIR__, '..', 'config/default.php'));
51 1
        if (Plateform::isPhar()) {
52
            $defaultConfig = Util::joinPath(Plateform::getPharPath(), 'config/default.php');
53
        }
54 1
        $this->data = new Data(include $defaultConfig);
55
56
        // import site config
57 1
        $this->siteConfig = $config;
58 1
        $this->importSiteConfig();
59
    }
60
61
    /**
62
     * Imports site configuration.
63
     */
64 1
    private function importSiteConfig(): void
65
    {
66 1
        $this->data->import($this->siteConfig, Data::REPLACE);
67
68
        /**
69
         * Overrides configuration with environment variables.
70
         */
71 1
        $data = $this->getData();
72 1
        $applyEnv = function ($array) use ($data) {
73 1
            $iterator = new \RecursiveIteratorIterator(
74 1
                new \RecursiveArrayIterator($array),
75 1
                \RecursiveIteratorIterator::SELF_FIRST
76 1
            );
77 1
            $iterator->rewind();
78 1
            while ($iterator->valid()) {
79 1
                $path = [];
80 1
                foreach (range(0, $iterator->getDepth()) as $depth) {
81 1
                    $path[] = $iterator->getSubIterator($depth)->key();
82
                }
83 1
                $sPath = implode('_', $path);
84 1
                if ($getEnv = getenv('CECIL_' . strtoupper($sPath))) {
85 1
                    $data->set(str_replace('_', '.', strtolower($sPath)), $this->castSetValue($getEnv));
86
                }
87 1
                $iterator->next();
88
            }
89 1
        };
90 1
        $applyEnv($data->export());
91
    }
92
93
    /**
94
     * Imports (theme) configuration.
95
     */
96 1
    public function import(array $config): void
97
    {
98 1
        $this->data->import($config, Data::REPLACE);
99
100
        // re-import site config
101 1
        $this->importSiteConfig();
102
103
        // checks the configuration
104 1
        $this->valid();
105
    }
106
107
    /**
108
     * Get configuration as an array.
109
     */
110
    public function getAsArray(): array
111
    {
112
        return $this->data->export();
113
    }
114
115
    /**
116
     * Is configuration's key exists?
117
     */
118 1
    public function has(string $key): bool
119
    {
120 1
        return $this->data->has($key);
121
    }
122
123
    /**
124
     * Get the value of a configuration's key.
125
     *
126
     * @param string $key      Configuration key
127
     * @param string $language Language code (optionnal)
128
     * @param bool   $fallback Set to false to not return the value in the default language as fallback
129
     *
130
     * @return mixed|null
131
     */
132 1
    public function get(string $key, ?string $language = null, bool $fallback = true)
133
    {
134 1
        if ($language !== null) {
135 1
            $langIndex = $this->getLanguageIndex($language);
136 1
            $keyLang = "languages.$langIndex.config.$key";
137 1
            if ($this->data->has($keyLang)) {
138 1
                return $this->data->get($keyLang);
139
            }
140 1
            if ($language !== $this->getLanguageDefault() && $fallback === false) {
141
                return null;
142
            }
143
        }
144 1
        if ($this->data->has($key)) {
145 1
            return $this->data->get($key);
146
        }
147
148 1
        return null;
149
    }
150
151
    /**
152
     * Set the source directory.
153
     *
154
     * @throws \InvalidArgumentException
155
     */
156 1
    public function setSourceDir(string $sourceDir = null): self
157
    {
158 1
        if ($sourceDir === null) {
159 1
            $sourceDir = getcwd();
160
        }
161 1
        if (!is_dir($sourceDir)) {
162
            throw new \InvalidArgumentException(\sprintf('The directory "%s" is not a valid source!', $sourceDir));
163
        }
164 1
        $this->sourceDir = $sourceDir;
165
166 1
        return $this;
167
    }
168
169
    /**
170
     * Get the source directory.
171
     */
172 1
    public function getSourceDir(): string
173
    {
174 1
        return $this->sourceDir;
175
    }
176
177
    /**
178
     * Set the destination directory.
179
     *
180
     * @throws \InvalidArgumentException
181
     */
182 1
    public function setDestinationDir(string $destinationDir = null): self
183
    {
184 1
        if ($destinationDir === null) {
185 1
            $destinationDir = $this->sourceDir;
186
        }
187 1
        if (!is_dir($destinationDir)) {
188
            throw new \InvalidArgumentException(\sprintf(
189
                'The directory "%s" is not a valid destination!',
190
                $destinationDir
191
            ));
192
        }
193 1
        $this->destinationDir = $destinationDir;
194
195 1
        return $this;
196
    }
197
198
    /**
199
     * Get the destination directory.
200
     */
201 1
    public function getDestinationDir(): string
202
    {
203 1
        return $this->destinationDir;
204
    }
205
206
    /*
207
     * Path helpers.
208
     */
209
210
    /**
211
     * Returns the path of the pages directory.
212
     */
213 1
    public function getPagesPath(): string
214
    {
215 1
        $path = Util::joinFile($this->getSourceDir(), (string) $this->get('pages.dir'));
216
217
        // legacy support
218 1
        if (!is_dir($path)) {
219
            $path = Util::joinFile($this->getSourceDir(), 'content');
220
        }
221
222 1
        return $path;
223
    }
224
225
    /**
226
     * Returns the path of the output directory.
227
     */
228 1
    public function getOutputPath(): string
229
    {
230 1
        return Util::joinFile($this->getDestinationDir(), (string) $this->get('output.dir'));
231
    }
232
233
    /**
234
     * Returns the path of the data directory.
235
     */
236 1
    public function getDataPath(): string
237
    {
238 1
        return Util::joinFile($this->getSourceDir(), (string) $this->get('data.dir'));
239
    }
240
241
    /**
242
     * Returns the path of templates directory.
243
     */
244 1
    public function getLayoutsPath(): string
245
    {
246 1
        return Util::joinFile($this->getSourceDir(), (string) $this->get('layouts.dir'));
247
    }
248
249
    /**
250
     * Returns the path of internal templates directory.
251
     */
252 1
    public function getLayoutsInternalPath(): string
253
    {
254 1
        return Util::joinPath(__DIR__, '..', (string) $this->get('layouts.internal.dir'));
255
    }
256
257
    /**
258
     * Returns the path of translations directory.
259
     */
260 1
    public function getTranslationsPath(): string
261
    {
262 1
        return Util::joinFile($this->getSourceDir(), (string) $this->get('layouts.translations.dir'));
263
    }
264
265
    /**
266
     * Returns the path of internal translations directory.
267
     */
268 1
    public function getTranslationsInternalPath(): string
269
    {
270 1
        if (Util\Plateform::isPhar()) {
271
            return Util::joinPath(Plateform::getPharPath(), (string) $this->get('translations.internal.dir'));
272
        }
273
274 1
        return realpath(Util::joinPath(__DIR__, '..', (string) $this->get('translations.internal.dir')));
275
    }
276
277
    /**
278
     * Returns the path of themes directory.
279
     */
280 1
    public function getThemesPath(): string
281
    {
282 1
        return Util::joinFile($this->getSourceDir(), (string) $this->get('themes.dir'));
283
    }
284
285
    /**
286
     * Returns the path of static files directory.
287
     */
288 1
    public function getStaticPath(): string
289
    {
290 1
        return Util::joinFile($this->getSourceDir(), (string) $this->get('static.dir'));
291
    }
292
293
    /**
294
     * Returns the path of static files directory, with a target.
295
     */
296 1
    public function getStaticTargetPath(): string
297
    {
298 1
        $path = $this->getStaticPath();
299
300 1
        if (!empty($this->get('static.target'))) {
301
            $path = substr($path, 0, -strlen((string) $this->get('static.target')));
302
        }
303
304 1
        return $path;
305
    }
306
307
    /**
308
     * Returns the path of assets files directory.
309
     */
310 1
    public function getAssetsPath(): string
311
    {
312 1
        return Util::joinFile($this->getSourceDir(), (string) $this->get('assets.dir'));
313
    }
314
315
    /**
316
     * Returns cache path.
317
     *
318
     * @throws RuntimeException
319
     */
320 1
    public function getCachePath(): string
321
    {
322 1
        if (empty((string) $this->get('cache.dir'))) {
323
            throw new RuntimeException(\sprintf('The cache directory ("%s") is not defined in configuration.', 'cache.dir'));
324
        }
325
326 1
        if ($this->isCacheDirIsAbsolute()) {
327
            $cacheDir = Util::joinFile((string) $this->get('cache.dir'), 'cecil');
328
            Util\File::getFS()->mkdir($cacheDir);
329
330
            return $cacheDir;
331
        }
332
333 1
        return Util::joinFile($this->getDestinationDir(), (string) $this->get('cache.dir'));
334
    }
335
336
    /**
337
     * Returns cache path of templates.
338
     */
339 1
    public function getCacheTemplatesPath(): string
340
    {
341 1
        return Util::joinFile($this->getCachePath(), (string) $this->get('cache.templates.dir'));
342
    }
343
344
    /**
345
     * Returns cache path of translations.
346
     */
347 1
    public function getCacheTranslationsPath(): string
348
    {
349 1
        return Util::joinFile($this->getCachePath(), (string) $this->get('cache.translations.dir'));
350
    }
351
352
    /**
353
     * Returns cache path of assets.
354
     */
355 1
    public function getCacheAssetsPath(): string
356
    {
357 1
        return Util::joinFile($this->getCachePath(), (string) $this->get('cache.assets.dir'));
358
    }
359
360
    /**
361
     * Returns cache path of remote assets.
362
     */
363 1
    public function getCacheAssetsRemotePath(): string
364
    {
365 1
        return Util::joinFile($this->getCacheAssetsPath(), (string) $this->get('cache.assets.remote.dir'));
366
    }
367
368
    /*
369
     * Output helpers.
370
     */
371
372
    /**
373
     * Returns the property value of an output format.
374
     *
375
     * @throws RuntimeException
376
     *
377
     * @return string|array|null
378
     */
379 1
    public function getOutputFormatProperty(string $name, string $property)
380
    {
381 1
        $properties = array_column((array) $this->get('output.formats'), $property, 'name');
382
383 1
        if (empty($properties)) {
384
            throw new RuntimeException(\sprintf('Property "%s" is not defined for format "%s".', $property, $name));
385
        }
386
387 1
        return $properties[$name] ?? null;
388
    }
389
390
    /*
391
     * Assets helpers.
392
     */
393
394
    /**
395
     * Returns asset image widths.
396
     */
397 1
    public function getAssetsImagesWidths(): array
398
    {
399 1
        return count((array) $this->get('assets.images.responsive.widths')) > 0 ? (array) $this->get('assets.images.responsive.widths') : [480, 640, 768, 1024, 1366, 1600, 1920];
400
    }
401
402
    /**
403
     * Returns asset image sizes.
404
     */
405 1
    public function getAssetsImagesSizes(): array
406
    {
407 1
        return count((array) $this->get('assets.images.responsive.sizes')) > 0 ? (array) $this->get('assets.images.responsive.sizes') : ['default' => '100vw'];
408
    }
409
410
    /*
411
     * Theme helpers.
412
     */
413
414
    /**
415
     * Returns theme(s) as an array.
416
     */
417 1
    public function getTheme(): ?array
418
    {
419 1
        if ($themes = $this->get('theme')) {
420 1
            if (is_array($themes)) {
421 1
                return $themes;
422
            }
423
424
            return [$themes];
425
        }
426
427
        return null;
428
    }
429
430
    /**
431
     * Has a (valid) theme(s)?
432
     *
433
     * @throws RuntimeException
434
     */
435 1
    public function hasTheme(): bool
436
    {
437 1
        if ($themes = $this->getTheme()) {
438 1
            foreach ($themes as $theme) {
439 1
                if (!Util\File::getFS()->exists($this->getThemeDirPath($theme, 'layouts')) && !Util\File::getFS()->exists(Util::joinFile($this->getThemesPath(), $theme, 'config.yml'))) {
440
                    throw new RuntimeException(\sprintf('Theme "%s" not found. Did you forgot to install it?', $theme));
441
                }
442
            }
443
444 1
            return true;
445
        }
446
447
        return false;
448
    }
449
450
    /**
451
     * Returns the path of a specific theme's directory.
452
     * ("layouts" by default).
453
     */
454 1
    public function getThemeDirPath(string $theme, string $dir = 'layouts'): string
455
    {
456 1
        return Util::joinFile($this->getThemesPath(), $theme, $dir);
457
    }
458
459
    /*
460
     * Language helpers.
461
     */
462
463
    /**
464
     * Returns an array of available languages.
465
     *
466
     * @throws RuntimeException
467
     */
468 1
    public function getLanguages(): array
469
    {
470 1
        if ($this->languages !== null) {
471 1
            return $this->languages;
472
        }
473
474 1
        $languages = (array) $this->get('languages');
475
476 1
        if (!is_int(array_search($this->getLanguageDefault(), array_column($languages, 'code')))) {
477
            throw new RuntimeException(\sprintf('The default language "%s" is not listed in "languages" key configuration.', $this->getLanguageDefault()));
478
        }
479
480 1
        $languages = array_filter($languages, function ($language) {
481 1
            return !(isset($language['enabled']) && $language['enabled'] === false);
482 1
        });
483
484 1
        $this->languages = $languages;
485
486 1
        return $this->languages;
487
    }
488
489
    /**
490
     * Returns the default language code (ie: "en", "fr-FR", etc.).
491
     *
492
     * @throws RuntimeException
493
     */
494 1
    public function getLanguageDefault(): string
495
    {
496 1
        if (!$this->get('language')) {
497
            throw new RuntimeException('There is no default "language" key in configuration.');
498
        }
499
500 1
        return $this->get('language');
501
    }
502
503
    /**
504
     * Returns a language code index.
505
     *
506
     * @throws RuntimeException
507
     */
508 1
    public function getLanguageIndex(string $code): int
509
    {
510 1
        $array = array_column($this->getLanguages(), 'code');
511
512 1
        if (false === $index = array_search($code, $array)) {
513
            throw new RuntimeException(\sprintf('The language code "%s" is not defined.', $code));
514
        }
515
516 1
        return $index;
517
    }
518
519
    /**
520
     * Returns the property value of a (specified or default) language.
521
     *
522
     * @throws RuntimeException
523
     */
524 1
    public function getLanguageProperty(string $property, ?string $code = null): string
525
    {
526 1
        $code = $code ?? $this->getLanguageDefault();
527
528 1
        $properties = array_column($this->getLanguages(), $property, 'code');
529
530 1
        if (empty($properties)) {
531
            throw new RuntimeException(\sprintf('Property "%s" is not defined for language "%s".', $property, $code));
532
        }
533
534 1
        return $properties[$code];
535
    }
536
537
    /*
538
     * Cache helpers.
539
     */
540
541
    /**
542
     * Is cache dir is absolute to system files
543
     * or relative to project destination?
544
     */
545 1
    public function isCacheDirIsAbsolute(): bool
546
    {
547 1
        $path = (string) $this->get('cache.dir');
548 1
        if (Util::joinFile($path) == realpath(Util::joinFile($path))) {
549
            return true;
550
        }
551
552 1
        return false;
553
    }
554
555
    /**
556
     * Set a Data object as configuration.
557
     */
558
    protected function setData(Data $data): self
559
    {
560
        if ($this->data !== $data) {
561
            $this->data = $data;
562
        }
563
564
        return $this;
565
    }
566
567
    /**
568
     * Get configuration as a Data object.
569
     */
570 1
    protected function getData(): Data
571
    {
572 1
        return $this->data;
573
    }
574
575
    /**
576
     * Valid the configuration.
577
     */
578 1
    private function valid(): void
579
    {
580
        // default language must be valid
581 1
        if (!preg_match('/^' . Config::LANG_CODE_PATTERN . '$/', (string) $this->get('language'))) {
582
            throw new ConfigException(\sprintf('Default language code "%s" is not valid (e.g.: "language: fr-FR").', $this->get('language')));
583
        }
584
        // if language is set then the locale is required
585 1
        foreach ((array) $this->get('languages') as $lang) {
586 1
            if (!isset($lang['locale'])) {
587
                throw new ConfigException('A language locale is not defined.');
588
            }
589 1
            if (!preg_match('/^' . Config::LANG_LOCALE_PATTERN . '$/', $lang['locale'])) {
590
                throw new ConfigException(\sprintf('The language locale "%s" is not valid (e.g.: "locale: fr_FR").', $lang['locale']));
591
            }
592
        }
593
        // checks pages config
594 1
        if ($this->has('defaultpages')) {
595
            throw new ConfigException(\sprintf("`defaultpages` must be moved to:\n%s", "pages:\n  default:\n    ..."));
596
        }
597 1
        if ($this->has('virtualpages')) {
598
            throw new ConfigException(\sprintf("`virtualpages` must be moved to:\n%s", "pages:\n  virtual:\n    ..."));
599
        }
600 1
        if ($this->has('generators')) {
601
            throw new ConfigException(\sprintf("`generators` must be moved to:\n%s", "pages:\n  generators:\n    ..."));
602
        }
603
        // checks layouts config
604 1
        if ($this->has('translations')) {
605
            throw new ConfigException(\sprintf("`translations` must be moved to:\n%s", "layouts:\n  translations:\n    ..."));
606
        }
607 1
        if ($this->has('extensions')) {
608
            throw new ConfigException(\sprintf("`extensions` must be moved to:\n%s", "layouts:\n  extensions:\n    ..."));
609
        }
610
    }
611
612
    /**
613
     * Casts boolean value given to set() as string.
614
     *
615
     * @param mixed $value
616
     *
617
     * @return bool|mixed
618
     */
619 1
    private function castSetValue($value)
620
    {
621 1
        if (is_string($value)) {
622
            switch ($value) {
623 1
                case 'true':
624 1
                    return true;
625 1
                case 'false':
626
                    return false;
627
                default:
628 1
                    return $value;
629
            }
630
        }
631
632
        return $value;
633
    }
634
}
635