Passed
Pull Request — master (#1676)
by Arnaud
09:56 queued 03:48
created

Config::getLanguageProperty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

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