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

Config::getTranslationsInternalPath()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

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