• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Cecilapp / Cecil / 5045835811

pending completion
5045835811

Pull #1697

github

GitHub
Merge c3e3c7443 into a16355c73
Pull Request #1697: perf: native_function_invocation

321 of 321 new or added lines in 62 files covered. (100.0%)

2784 of 4121 relevant lines covered (67.56%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.57
/src/Config.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <arnaud@ligny.fr>
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
    public function __construct(?array $config = null)
47
    {
48
        // load default configuration
49
        $defaultConfig = realpath(Util::joinFile(__DIR__, '..', 'config/default.php'));
1✔
50
        if (Plateform::isPhar()) {
1✔
51
            $defaultConfig = Util::joinPath(Plateform::getPharPath(), 'config/default.php');
×
52
        }
53
        $this->data = new Data(include $defaultConfig);
1✔
54

55
        // import site config
56
        $this->siteConfig = $config;
1✔
57
        $this->importSiteConfig();
1✔
58
    }
59

60
    /**
61
     * Imports site configuration.
62
     */
63
    private function importSiteConfig(): void
64
    {
65
        $this->data->import($this->siteConfig);
1✔
66

67
        /**
68
         * Overrides configuration with environment variables.
69
         */
70
        $data = $this->getData();
1✔
71
        $applyEnv = function ($array) use ($data) {
1✔
72
            $iterator = new \RecursiveIteratorIterator(
1✔
73
                new \RecursiveArrayIterator($array),
1✔
74
                \RecursiveIteratorIterator::SELF_FIRST
1✔
75
            );
1✔
76
            $iterator->rewind();
1✔
77
            while ($iterator->valid()) {
1✔
78
                $path = [];
1✔
79
                foreach (range(0, $iterator->getDepth()) as $depth) {
1✔
80
                    $path[] = $iterator->getSubIterator($depth)->key();
1✔
81
                }
82
                $sPath = implode('_', $path);
1✔
83
                if ($getEnv = getenv('CECIL_' . strtoupper($sPath))) {
1✔
84
                    $data->set(str_replace('_', '.', strtolower($sPath)), $this->castSetValue($getEnv));
1✔
85
                }
86
                $iterator->next();
1✔
87
            }
88
        };
1✔
89
        $applyEnv($data->export());
1✔
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)) {
1✔
102
            switch ($value) {
103
                case 'true':
1✔
104
                    return true;
1✔
105
                case 'false':
1✔
106
                    return false;
×
107
                default:
108
                    return $value;
1✔
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);
1✔
121

122
        // re-import site config
123
        $this->importSiteConfig();
1✔
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;
1✔
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);
1✔
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) {
1✔
174
            $langIndex = $this->getLanguageIndex($language);
1✔
175
            $keyLang = "languages.$langIndex.config.$key";
1✔
176
            if ($this->data->has($keyLang)) {
1✔
177
                return $this->data->get($keyLang);
1✔
178
            }
179
            if ($language !== $this->getLanguageDefault() && $fallback === false) {
1✔
180
                return null;
×
181
            }
182
        }
183
        if ($this->data->has($key)) {
1✔
184
            return $this->data->get($key);
1✔
185
        }
186

187
        return null;
1✔
188
    }
189

190
    /**
191
     * Set the source directory.
192
     *
193
     * @throws \InvalidArgumentException
194
     */
195
    public function setSourceDir(string $sourceDir = null): self
196
    {
197
        if ($sourceDir === null) {
1✔
198
            $sourceDir = getcwd();
1✔
199
        }
200
        if (!is_dir($sourceDir)) {
1✔
201
            throw new \InvalidArgumentException(sprintf('The directory "%s" is not a valid source!', $sourceDir));
×
202
        }
203
        $this->sourceDir = $sourceDir;
1✔
204

205
        return $this;
1✔
206
    }
207

208
    /**
209
     * Get the source directory.
210
     */
211
    public function getSourceDir(): string
212
    {
213
        return $this->sourceDir;
1✔
214
    }
215

216
    /**
217
     * Set the destination directory.
218
     *
219
     * @throws \InvalidArgumentException
220
     */
221
    public function setDestinationDir(string $destinationDir = null): self
222
    {
223
        if ($destinationDir === null) {
1✔
224
            $destinationDir = $this->sourceDir;
1✔
225
        }
226
        if (!is_dir($destinationDir)) {
1✔
227
            throw new \InvalidArgumentException(sprintf(
×
228
                'The directory "%s" is not a valid destination!',
×
229
                $destinationDir
×
230
            ));
×
231
        }
232
        $this->destinationDir = $destinationDir;
1✔
233

234
        return $this;
1✔
235
    }
236

237
    /**
238
     * Get the destination directory.
239
     */
240
    public function getDestinationDir(): string
241
    {
242
        return $this->destinationDir;
1✔
243
    }
244

245
    /**
246
     * Path helpers.
247
     */
248

249
    /**
250
     * Returns the path of the pages directory.
251
     */
252
    public function getPagesPath(): string
253
    {
254
        $path = Util::joinFile($this->getSourceDir(), (string) $this->get('pages.dir'));
1✔
255

256
        // legacy support
257
        if (!is_dir($path)) {
1✔
258
            $path = Util::joinFile($this->getSourceDir(), 'content');
×
259
        }
260

261
        return $path;
1✔
262
    }
263

264
    /**
265
     * Returns the path of the data directory.
266
     */
267
    public function getDataPath(): string
268
    {
269
        return Util::joinFile($this->getSourceDir(), (string) $this->get('data.dir'));
1✔
270
    }
271

272
    /**
273
     * Returns the path of templates directory.
274
     */
275
    public function getLayoutsPath(): string
276
    {
277
        return Util::joinFile($this->getSourceDir(), (string) $this->get('layouts.dir'));
1✔
278
    }
279

280
    /**
281
     * Returns the path of themes directory.
282
     */
283
    public function getThemesPath(): string
284
    {
285
        return Util::joinFile($this->getSourceDir(), (string) $this->get('themes.dir'));
1✔
286
    }
287

288
    /**
289
     * Returns the path of internal templates directory.
290
     */
291
    public function getInternalLayoutsPath(): string
292
    {
293
        return Util::joinPath(__DIR__, '..', (string) $this->get('layouts.internal.dir'));
1✔
294
    }
295

296
    /**
297
     * Returns the path of translations directory.
298
     */
299
    public function getTranslationsPath(): string
300
    {
301
        return Util::joinFile($this->getSourceDir(), (string) $this->get('translations.dir'));
1✔
302
    }
303

304
    /**
305
     * Returns the path of internal translations directory.
306
     */
307
    public function getInternalTranslationsPath(): string
308
    {
309
        if (Util\Plateform::isPhar()) {
1✔
310
            return Util::joinPath(Plateform::getPharPath(), (string) $this->get('translations.internal.dir'));
×
311
        }
312

313
        return realpath(Util::joinPath(__DIR__, '..', (string) $this->get('translations.internal.dir')));
1✔
314
    }
315

316
    /**
317
     * Returns the path of the output directory.
318
     */
319
    public function getOutputPath(): string
320
    {
321
        return Util::joinFile($this->getDestinationDir(), (string) $this->get('output.dir'));
1✔
322
    }
323

324
    /**
325
     * Returns the path of static files directory.
326
     */
327
    public function getStaticPath(): string
328
    {
329
        return Util::joinFile($this->getSourceDir(), (string) $this->get('static.dir'));
1✔
330
    }
331

332
    /**
333
     * Returns the path of static files directory, with a target.
334
     */
335
    public function getStaticTargetPath(): string
336
    {
337
        $path = $this->getStaticPath();
1✔
338

339
        if (!empty($this->get('static.target'))) {
1✔
340
            $path = substr($path, 0, -\strlen((string) $this->get('static.target')));
×
341
        }
342

343
        return $path;
1✔
344
    }
345

346
    /**
347
     * Returns the path of assets files directory.
348
     */
349
    public function getAssetsPath(): string
350
    {
351
        return Util::joinFile($this->getSourceDir(), (string) $this->get('assets.dir'));
1✔
352
    }
353

354
    /**
355
     * Returns asset image widths.
356
     */
357
    public function getAssetsImagesWidths(): array
358
    {
359
        return \count((array) $this->get('assets.images.responsive.widths')) > 0 ? (array) $this->get('assets.images.responsive.widths') : [480, 640, 768, 1024, 1366, 1600, 1920];
1✔
360
    }
361

362
    /**
363
     * Returns asset image sizes.
364
     */
365
    public function getAssetsImagesSizes(): array
366
    {
367
        return \count((array) $this->get('assets.images.responsive.sizes')) > 0 ? (array) $this->get('assets.images.responsive.sizes') : ['default' => '100vw'];
1✔
368
    }
369

370
    /**
371
     * Is cache dir is absolute to system files
372
     * or relative to project destination?
373
     */
374
    public function isCacheDirIsAbsolute(): bool
375
    {
376
        $path = (string) $this->get('cache.dir');
1✔
377
        if (Util::joinFile($path) == realpath(Util::joinFile($path))) {
1✔
378
            return true;
×
379
        }
380

381
        return false;
1✔
382
    }
383

384
    /**
385
     * Returns cache path.
386
     *
387
     * @throws RuntimeException
388
     */
389
    public function getCachePath(): string
390
    {
391
        if (empty((string) $this->get('cache.dir'))) {
1✔
392
            throw new RuntimeException(sprintf('The cache directory ("%s") is not defined in configuration.', 'cache.dir'));
×
393
        }
394

395
        if ($this->isCacheDirIsAbsolute()) {
1✔
396
            $cacheDir = Util::joinFile((string) $this->get('cache.dir'), 'cecil');
×
397
            Util\File::getFS()->mkdir($cacheDir);
×
398

399
            return $cacheDir;
×
400
        }
401

402
        return Util::joinFile($this->getDestinationDir(), (string) $this->get('cache.dir'));
1✔
403
    }
404

405
    /**
406
     * Returns cache path of templates.
407
     */
408
    public function getCacheTemplatesPath(): string
409
    {
410
        return Util::joinFile($this->getCachePath(), (string) $this->get('cache.templates.dir'));
1✔
411
    }
412

413
    /**
414
     * Returns cache path of translations.
415
     */
416
    public function getCacheTranslationsPath(): string
417
    {
418
        return Util::joinFile($this->getCachePath(), (string) $this->get('cache.translations.dir'));
1✔
419
    }
420

421
    /**
422
     * Returns cache path of assets.
423
     */
424
    public function getCacheAssetsPath(): string
425
    {
426
        return Util::joinFile($this->getCachePath(), (string) $this->get('cache.assets.dir'));
1✔
427
    }
428

429
    /**
430
     * Returns cache path of remote assets.
431
     */
432
    public function getCacheAssetsRemotePath(): string
433
    {
434
        return Util::joinFile($this->getCacheAssetsPath(), (string) $this->get('cache.assets.remote.dir'));
1✔
435
    }
436

437
    /**
438
     * Returns the property value of an output format.
439
     *
440
     * @throws RuntimeException
441
     *
442
     * @return string|array|null
443
     */
444
    public function getOutputFormatProperty(string $name, string $property)
445
    {
446
        $properties = array_column((array) $this->get('output.formats'), $property, 'name');
1✔
447

448
        if (empty($properties)) {
1✔
449
            throw new RuntimeException(sprintf('Property "%s" is not defined for format "%s".', $property, $name));
×
450
        }
451

452
        return $properties[$name] ?? null;
1✔
453
    }
454

455
    /**
456
     * Theme helpers.
457
     */
458

459
    /**
460
     * Returns theme(s) as an array.
461
     */
462
    public function getTheme(): ?array
463
    {
464
        if ($themes = $this->get('theme')) {
1✔
465
            if (\is_array($themes)) {
1✔
466
                return $themes;
1✔
467
            }
468

469
            return [$themes];
×
470
        }
471

472
        return null;
×
473
    }
474

475
    /**
476
     * Has a (valid) theme(s)?
477
     *
478
     * @throws RuntimeException
479
     */
480
    public function hasTheme(): bool
481
    {
482
        if ($themes = $this->getTheme()) {
1✔
483
            foreach ($themes as $theme) {
1✔
484
                if (!Util\File::getFS()->exists($this->getThemeDirPath($theme, 'layouts')) && !Util\File::getFS()->exists(Util::joinFile($this->getThemesPath(), $theme, 'config.yml'))) {
1✔
485
                    throw new RuntimeException(sprintf('Theme "%s" not found. Did you forgot to install it?', $theme));
×
486
                }
487
            }
488

489
            return true;
1✔
490
        }
491

492
        return false;
×
493
    }
494

495
    /**
496
     * Returns the path of a specific theme's directory.
497
     * ("layouts" by default).
498
     */
499
    public function getThemeDirPath(string $theme, string $dir = 'layouts'): string
500
    {
501
        return Util::joinFile($this->getThemesPath(), $theme, $dir);
1✔
502
    }
503

504
    /**
505
     * Language helpers.
506
     */
507

508
    /**
509
     * Returns an array of available languages.
510
     *
511
     * @throws RuntimeException
512
     */
513
    public function getLanguages(): array
514
    {
515
        if ($this->languages !== null) {
1✔
516
            return $this->languages;
1✔
517
        }
518

519
        $languages = (array) $this->get('languages');
1✔
520

521
        if (!\is_int(array_search($this->getLanguageDefault(), array_column($languages, 'code')))) {
1✔
522
            throw new RuntimeException(sprintf('The default language "%s" is not listed in "languages" key configuration.', $this->getLanguageDefault()));
×
523
        }
524

525
        $languages = array_filter($languages, function ($language) {
1✔
526
            return !(isset($language['enabled']) && $language['enabled'] === false);
1✔
527
        });
1✔
528

529
        $this->languages = $languages;
1✔
530

531
        return $this->languages;
1✔
532
    }
533

534
    /**
535
     * Returns the default language code (ie: "en", "fr-FR", etc.).
536
     *
537
     * @throws RuntimeException
538
     */
539
    public function getLanguageDefault(): string
540
    {
541
        if (!$this->get('language')) {
1✔
542
            throw new RuntimeException('There is no default "language" key in configuration.');
×
543
        }
544

545
        return $this->get('language');
1✔
546
    }
547

548
    /**
549
     * Returns a language code index.
550
     *
551
     * @throws RuntimeException
552
     */
553
    public function getLanguageIndex(string $code): int
554
    {
555
        $array = array_column($this->getLanguages(), 'code');
1✔
556

557
        if (false === $index = array_search($code, $array)) {
1✔
558
            throw new RuntimeException(sprintf('The language code "%s" is not defined.', $code));
×
559
        }
560

561
        return $index;
1✔
562
    }
563

564
    /**
565
     * Returns the property value of a (specified or default) language.
566
     *
567
     * @throws RuntimeException
568
     */
569
    public function getLanguageProperty(string $property, ?string $code = null): string
570
    {
571
        $code = $code ?? $this->getLanguageDefault();
1✔
572

573
        $properties = array_column($this->getLanguages(), $property, 'code');
1✔
574

575
        if (empty($properties)) {
1✔
576
            throw new RuntimeException(sprintf('Property "%s" is not defined for language "%s".', $property, $code));
×
577
        }
578

579
        return $properties[$code];
1✔
580
    }
581
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc