Passed
Push — cache-assets ( 7e7a48...4621d7 )
by Arnaud
08:54 queued 04:23
created

Config::getCacheAssetsRemotePath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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