From da3956456eb208121acc1a5591a80b4e95469776 Mon Sep 17 00:00:00 2001 From: Arnaud Ligny Date: Tue, 26 Nov 2024 01:25:28 +0100 Subject: [PATCH] refactor: rebuild configuration (#2068) --- config/base.php | 174 ++++++++++++++++ config/default.php | 353 +++++++++----------------------- src/Assets/Asset.php | 16 +- src/Builder.php | 23 +-- src/Command/AbstractCommand.php | 6 +- src/Config.php | 231 +++++++++++---------- src/Converter/Parsedown.php | 15 +- src/Generator/Pagination.php | 4 +- src/Renderer/Extension/Core.php | 6 +- src/Renderer/Twig.php | 2 +- src/Step/Optimize/Images.php | 2 +- src/Step/Pages/Convert.php | 2 +- src/Step/Pages/Render.php | 2 +- src/Step/Themes/Import.php | 5 +- 14 files changed, 421 insertions(+), 420 deletions(-) create mode 100644 config/base.php diff --git a/config/base.php b/config/base.php new file mode 100644 index 000000000..6456fbc82 --- /dev/null +++ b/config/base.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// Base configuration +return [ + 'taxonomies' => [ + 'tags' => 'tag', + 'categories' => 'category', + ], + 'pages' => [ + 'generators' => [ + 10 => 'Cecil\Generator\DefaultPages', + 20 => 'Cecil\Generator\VirtualPages', + 30 => 'Cecil\Generator\ExternalBody', + 40 => 'Cecil\Generator\Section', + 50 => 'Cecil\Generator\Taxonomy', + 60 => 'Cecil\Generator\Homepage', + 70 => 'Cecil\Generator\Pagination', + 80 => 'Cecil\Generator\Alias', + 90 => 'Cecil\Generator\Redirect', + ], + 'default' => [ + 'index' => [ + 'path' => '', + 'title' => 'Home', + 'published' => true, + ], + '404' => [ + 'path' => '404', + 'title' => 'Page not found', + 'layout' => '404', + 'uglyurl' => true, + 'published' => true, + 'exclude' => true, + ], + 'robots' => [ + 'path' => 'robots', + 'title' => 'Robots.txt', + 'layout' => 'robots', + 'output' => 'txt', + 'published' => true, + 'exclude' => true, + 'multilingual' => false, + ], + 'sitemap' => [ + 'path' => 'sitemap', + 'title' => 'XML sitemap', + 'layout' => 'sitemap', + 'output' => 'xml', + 'changefreq' => 'monthly', + 'priority' => '0.5', + 'published' => true, + 'exclude' => true, + 'multilingual' => false, + ], + 'xsl/atom' => [ + 'path' => 'xsl/atom', + 'layout' => 'feed', + 'output' => 'xsl', + 'uglyurl' => true, + 'published' => true, + 'exclude' => true, + ], + 'xsl/rss' => [ + 'path' => 'xsl/rss', + 'layout' => 'feed', + 'output' => 'xsl', + 'uglyurl' => true, + 'published' => false, + 'exclude' => true, + ], + ], + ], + 'output' => [ + 'formats' => [ + [ // e.g.: blog/post-1/index.html + 'name' => 'html', + 'mediatype' => 'text/html', + 'filename' => 'index', + 'extension' => 'html', + ], + [ // e.g.: blog/atom.xml + 'name' => 'atom', + 'mediatype' => 'application/atom+xml', + 'filename' => 'atom', + 'extension' => 'xml', + 'exclude' => ['redirect', 'paginated'], + ], + [ // e.g.: blog/rss.xml + 'name' => 'rss', + 'mediatype' => 'application/rss+xml', + 'filename' => 'rss', + 'extension' => 'xml', + 'exclude' => ['redirect', 'paginated'], + ], + [ // e.g.: blog.json + 'name' => 'json', + 'mediatype' => 'application/json', + 'extension' => 'json', + 'exclude' => ['redirect'], + ], + [ // e.g.: blog.xml + 'name' => 'xml', + 'mediatype' => 'application/xml', + 'extension' => 'xml', + 'exclude' => ['redirect'], + ], + [ // e.g.: robots.txt + 'name' => 'txt', + 'mediatype' => 'text/plain', + 'extension' => 'txt', + 'exclude' => ['redirect'], + ], + [ // e.g.: blog/post-1/amp/index.html + 'name' => 'amp', + 'mediatype' => 'text/html', + 'subpath' => 'amp', + 'filename' => 'index', + 'extension' => 'html', + ], + [ // e.g.: sw.js + 'name' => 'js', + 'mediatype' => 'application/javascript', + 'extension' => 'js', + ], + [ // e.g.: manifest.webmanifest + 'name' => 'webmanifest', + 'mediatype' => 'application/manifest+json', + 'extension' => 'webmanifest', + ], + [ // e.g.: atom.xsl + 'name' => 'xsl', + 'mediatype' => 'application/xml', + 'extension' => 'xsl', + ], + [ // e.g.: blog/feed.json + 'name' => 'jsonfeed', + 'mediatype' => 'application/json', + 'filename' => 'feed', + 'extension' => 'json', + 'exclude' => ['redirect', 'paginated'], + ], + [ // e.g.: video/embed.html + 'name' => 'iframe', + 'mediatype' => 'text/html', + 'filename' => 'embed', + 'extension' => 'html', + 'exclude' => ['redirect', 'paginated'], + ], + [ // e.g.: video/embed.json + 'name' => 'oembed', + 'mediatype' => 'application/json+oembed', + 'filename' => 'embed', + 'extension' => 'json', + 'exclude' => ['redirect', 'paginated'], + ], + ], + 'postprocessors' => [ + 'GeneratorMetaTag' => 'Cecil\Renderer\PostProcessor\GeneratorMetaTag', + 'HtmlExcerpt' => 'Cecil\Renderer\PostProcessor\HtmlExcerpt', + 'MarkdownLink' => 'Cecil\Renderer\PostProcessor\MarkdownLink', + ], + ], +]; diff --git a/config/default.php b/config/default.php index f0ae73317..a3584ebad 100644 --- a/config/default.php +++ b/config/default.php @@ -11,33 +11,33 @@ * file that was distributed with this source code. */ -// Website default configuration +// Default configuration return [ 'title' => 'Site title', 'baseline' => '', - 'baseurl' => 'http://localhost:8000/', + 'baseurl' => '', // e.g.: https://cecil.app/ 'canonicalurl' => false, // if true then `url()` function prepends URL with `baseurl` 'description' => 'Site description.', 'author' => [ - //'name' => '', - //'url' => '', + 'name' => 'Cecil', + 'url' => 'https://cecil.app', //'email' => '', ], - //'image' => '', // OG image - 'social' => [ - //'social_network' => [ - // 'username' => '', - // 'url' => '', - //] - ], + //'image' => '', // `og:image` + //'social' => [ + // 'social_network' => [ + // 'username' => '', + // 'url' => '', + // ] + //], 'date' => [ 'format' => 'F j, Y', // @see https://www.php.net/manual/fr/datetime.format.php#refsect1-datetime.format-parameters //'timezone' => 'Europe/Paris', ], - 'language' => 'en', // main language code (`en` by default) - //'language' => [ + 'language' => 'en', // main language code + //'language' => [ // advanced language options // 'code' => 'en', - // 'prefix' => false, // use `true` to apply language code prefix to default language pages path (`false` by default) + // 'prefix' => false, // use `true` to apply language code prefix to default language pages path //], 'languages' => [ [ @@ -47,64 +47,62 @@ ], ], 'theme' => [], // no theme(s) by default - 'taxonomies' => [ // default taxonomies - 'tags' => 'tag', - 'categories' => 'category', // can be disabled with the special "disabled" value - ], + //'taxonomies' => [ // can be disabled with the special "disabled" value + // '' => '', + //], 'pagination' => [ 'max' => 5, // number of pages by each paginated pages 'path' => 'page', // path to paginated pages (e.g.: `/blog/page/2/`) ], - // Markdown content management - 'pages' => [ - 'dir' => 'pages', // pages files directory (`pages` by default, previously `content`) + 'pages' => [ // Markdown content management + 'dir' => 'pages', // pages files directory 'ext' => ['md', 'markdown', 'mdown', 'mkdn', 'mkd', 'text', 'txt'], // supported files formats, by extension 'exclude' => ['vendor', 'node_modules'], // directories, paths and files name to exclude (accepts globs, strings and regexes) - 'sortby' => 'date', // default collections sort method - //'sortby' => [ + 'sortby' => 'date', // collections sort method + //'sortby' => [ // advanced sort options // 'variable' => 'date', // date|updated|title|weight // 'desc_title' => false, // false|true // 'reverse' => false, // false|true //], 'frontmatter' => [ - 'format' => 'yaml', // front matter format: `yaml`, `ini`, `toml` or `json` (`yaml` by default) + 'format' => 'yaml', // front matter format: `yaml`, `ini`, `toml` or `json` ], 'body' => [ 'format' => 'markdown', // page body format (only Markdown is supported) 'toc' => ['h2', 'h3'], // headers used to build the table of contents 'highlight' => [ - 'enabled' => false, // enables code syntax highlighting (`false` by default) + 'enabled' => false, // enables code syntax highlighting ], 'images' => [ 'lazy' => [ - 'enabled' => true, // adds `loading="lazy"` attribute (`true` by default) + 'enabled' => true, // adds `loading="lazy"` attribute ], 'decoding' => [ - 'enabled' => true, // adds `decoding="async"` attribute (`true` by default) + 'enabled' => true, // adds `decoding="async"` attribute ], 'resize' => [ - 'enabled' => false, // enables image resizing by using the `width` extra attribute (`false` by default) + 'enabled' => false, // enables image resizing by using the `width` extra attribute ], - 'formats' => [], // creates and adds formats images as `source` (empty by default) + 'formats' => [], // creates and adds formats images as `source` 'responsive' => [ - 'enabled' => false, // creates responsive images and adds them to the `srcset` attribute (`false` by default) + 'enabled' => false, // creates responsive images and adds them to the `srcset` attribute ], - 'class' => '', // puts default CSS class to each image (empty by default) + 'class' => '', // puts default CSS class to each image 'caption' => [ - 'enabled' => false, // puts the image in a
element and adds a
containing the title (`false` by default) + 'enabled' => false, // puts the image in a
element and adds a
containing the title ], 'remote' => [ - 'enabled' => true, // turns remote images to Asset to handling them (`true` by default) + 'enabled' => true, // turns remote images to Asset to handling them 'fallback' => [ - 'enabled' => false, // enables a fallback if image is not found (`false` by default) - 'path' => '', // path to the fallback image, stored in assets dir (empty by default) + 'enabled' => false, // enables a fallback if image is not found + 'path' => '', // path to the fallback image, stored in assets dir ], ], - 'placeholder' => '', // fill background before loading ('color' or 'lqip', empty by default) + 'placeholder' => '', // fill background before loading ('color' or 'lqip') ], 'links' => [ 'embed' => [ - 'enabled' => false, // turns links in embedded content if possible (`false` by default) + 'enabled' => false, // turns links in embedded content if possible 'video' => [ 'ext' => ['mp4', 'webm'], // supported video file types, extensions ], @@ -114,129 +112,65 @@ ], 'external' => [ 'blank' => false, // if true open external link in new tab - 'noopener' => true, // add "noopener" to `rel` attribute - 'noreferrer' => true, // add "noreferrer" to `rel` attribute - 'nofollow' => true, // add "nofollow" to `rel` attribute + 'noopener' => true, // add "noopener" to `rel` attribute + 'noreferrer' => true, // add "noreferrer" to `rel` attribute + 'nofollow' => true, // add "nofollow" to `rel` attribute ] ], 'excerpt' => [ - 'separator' => 'excerpt|break', // string to use as separator (`excerpt|break` by default) - 'capture' => 'before', // part to capture, `before` or `after` the separator (`before` by default) - ], - ], - 'generators' => [ // list of pages generators class, ordered by weight - 10 => 'Cecil\Generator\DefaultPages', - 20 => 'Cecil\Generator\VirtualPages', - 30 => 'Cecil\Generator\ExternalBody', - 40 => 'Cecil\Generator\Section', - 50 => 'Cecil\Generator\Taxonomy', - 60 => 'Cecil\Generator\Homepage', - 70 => 'Cecil\Generator\Pagination', - 80 => 'Cecil\Generator\Alias', - 90 => 'Cecil\Generator\Redirect', - ], - 'default' => [ // default generated pages - 'index' => [ - 'path' => '', - 'title' => 'Home', - 'published' => true, - ], - '404' => [ - 'path' => '404', - 'title' => 'Page not found', - 'layout' => '404', - 'uglyurl' => true, - 'published' => true, - 'exclude' => true, - ], - 'robots' => [ - 'path' => 'robots', - 'title' => 'Robots.txt', - 'layout' => 'robots', - 'output' => 'txt', - 'published' => true, - 'exclude' => true, - 'multilingual' => false, - ], - 'sitemap' => [ - 'path' => 'sitemap', - 'title' => 'XML sitemap', - 'layout' => 'sitemap', - 'output' => 'xml', - 'changefreq' => 'monthly', - 'priority' => '0.5', - 'published' => true, - 'exclude' => true, - 'multilingual' => false, - ], - 'xsl/atom' => [ - 'path' => 'xsl/atom', - 'layout' => 'feed', - 'output' => 'xsl', - 'uglyurl' => true, - 'published' => true, - 'exclude' => true, - ], - 'xsl/rss' => [ - 'path' => 'xsl/rss', - 'layout' => 'feed', - 'output' => 'xsl', - 'uglyurl' => true, - 'published' => false, - 'exclude' => true, + 'separator' => 'excerpt|break', // string to use as separator + 'capture' => 'before', // part to capture, `before` or `after` the separator ], ], + //'generators' => [ // list of pages generators class, ordered by weight + // => 'Cecil\Generator\', + //], ], - // data files - 'data' => [ - 'dir' => 'data', // data files directory (`data` by default) + 'data' => [ // data files + 'dir' => 'data', // data files directory 'ext' => ['yaml', 'yml', 'json', 'xml', 'csv'], // loaded files by extension 'load' => true, // enables `site.data` collection ], - // static files - 'static' => [ - 'dir' => 'static', // static files directory (`static` by default) + 'static' => [ // static files + 'dir' => 'static', // static files directory 'target' => '', // subdirectory where files are copied 'exclude' => ['sass', 'scss', '*.scss', 'package*.json', 'node_modules'], // excluded files by extension or pattern 'load' => false, // enables `site.static` collection ], - // assets: CSS, JS, images... - 'assets' => [ - 'dir' => 'assets', // assets files directory (`assets` by default) + 'assets' => [ // assets: CSS, JS, images, etc. + 'dir' => 'assets', // assets files directory 'target' => 'assets', // where remote and resized assets are saved 'fingerprint' => [ - 'enabled' => true, // enables fingerprinting (`true` by default) + 'enabled' => true, // enables fingerprinting ], 'compile' => [ - 'enabled' => true, // enables Sass files compilation (`true` by default) - 'style' => 'expanded', // compilation style (`expanded` or `compressed`, `expanded` by default) - 'import' => ['sass', 'scss', 'node_modules'], // list of imported paths (`[sass, scss, node_modules]` by default) - 'sourcemap' => false, // enables sourcemap in debug mode (`false` by default) - //'variables' => ['var' => 'value'], // list of preset variables (empty by default) + 'enabled' => true, // enables Sass files compilation + 'style' => 'expanded', // compilation style (`expanded` or `compressed`) + 'import' => ['sass', 'scss', 'node_modules'], // list of imported paths + 'sourcemap' => false, // enables sourcemap in debug mode + //'variables' => ['' => ''], // list of preset variables ], 'minify' => [ - 'enabled' => true, // enables CSS et JS minification (`true` by default) + 'enabled' => true, // enables CSS et JS minification ], 'images' => [ 'resize' => [ - 'dir' => 'thumbnails', // where resized images are stored (`thumbnails` by default) + 'dir' => 'thumbnails', // where resized images are stored ], 'optimize' => [ - 'enabled' => false, // enables images optimization with JpegOptim, Optipng, Pngquant 2, SVGO 1, Gifsicle, cwebp, avifenc (`false` by default) + 'enabled' => false, // enables images optimization with JpegOptim, Optipng, Pngquant 2, SVGO 1, Gifsicle, cwebp, avifenc ], - 'quality' => 75, // image quality after optimization or resize (`75` by default) + 'quality' => 75, // image quality after optimization or resize 'responsive' => [ - 'widths' => [], // `srcset` widths (`[480, 640, 768, 1024, 1366, 1600, 1920]` - 'sizes' => [ - 'default' => '100vw', // default `sizes` attribute (`100vw` by default) - ], - 'enabled' => false, // `html` filter: creates responsive images (`false` by default) + 'enabled' => false, // `html` filter: creates responsive images + 'widths' => [480, 640, 768, 1024, 1366, 1600, 1920], // `srcset` widths + 'sizes' => ['default' => '100vw'] // default `sizes` attribute ], - 'formats' => [], // `html` filter: creates and adds formats images as `source` (empty by default) + 'formats' => [], // `html` filter: creates and adds formats images as `source` (ie "webp" and/or "avif") 'cdn' => [ - 'enabled' => false, // enables Image CDN (`false` by default) - 'canonical' => true, // is `image_url` must be canonical or not (`true` by default) - 'remote' => true, // includes remote images (`true` by default) + 'enabled' => false, // enables Image CDN + 'canonical' => true, // is `image_url` must be canonical or not + 'remote' => true, // includes remote images //'account' => 'xxxx', // provider account // Cloudinary //'url' => 'https://res.cloudinary.com/%account%/image/fetch/c_limit,w_%width%,q_%quality%,f_%format%,d_default/%image_url%', @@ -249,130 +183,39 @@ ] ], ], - // layouts and templates - 'layouts' => [ - 'dir' => 'layouts', // Twig templates directory (`layouts` by default) + 'layouts' => [ // layouts and templates + 'dir' => 'layouts', // Twig templates directory 'internal' => [ 'dir' => 'resources/layouts', // internal templates directory ], 'translations' => [ // i18n - 'dir' => 'translations', // translations files directory (`translations` by default) - 'formats' => ['yaml', 'mo'], // translations supported formats (`yaml` and `mo`) + 'dir' => 'translations', // translations files directory + 'formats' => ['yaml', 'mo'], // translations supported formats 'internal' => [ 'dir' => 'resources/translations', // internal translations directory ], ], 'extensions' => [ // list of Twig extensions class - //'Name' => 'Cecil\Renderer\Extension\Class', + //'' => 'Cecil\Renderer\Extension\', ], ], - // themes 'themes' => [ - 'dir' => 'themes', // where themes are stored (`themes` by default) + 'dir' => 'themes', // where themes are stored ], - // SEO robots default directive 'metatags' => [ - 'robots' => 'index,follow', + 'robots' => 'index,follow', // SEO robots default directive ], - // output formats and post process - 'output' => [ - 'dir' => '_site', // output directory (`_site` by default) - 'formats' => [ // https://cecil.app/documentation/configuration/#formats - // e.g.: blog/post-1/index.html - -1 => [ - 'name' => 'html', - 'mediatype' => 'text/html', - 'filename' => 'index', - 'extension' => 'html', - ], - // e.g.: blog/atom.xml - -2 => [ - 'name' => 'atom', - 'mediatype' => 'application/atom+xml', - 'filename' => 'atom', - 'extension' => 'xml', - 'exclude' => ['redirect', 'paginated'], - ], - // e.g.: blog/rss.xml - -3 => [ - 'name' => 'rss', - 'mediatype' => 'application/rss+xml', - 'filename' => 'rss', - 'extension' => 'xml', - 'exclude' => ['redirect', 'paginated'], - ], - // e.g.: blog.json - -4 => [ - 'name' => 'json', - 'mediatype' => 'application/json', - 'extension' => 'json', - 'exclude' => ['redirect'], - ], - // e.g.: blog.xml - -5 => [ - 'name' => 'xml', - 'mediatype' => 'application/xml', - 'extension' => 'xml', - 'exclude' => ['redirect'], - ], - // e.g.: robots.txt - -6 => [ - 'name' => 'txt', - 'mediatype' => 'text/plain', - 'extension' => 'txt', - 'exclude' => ['redirect'], - ], - // e.g.: blog/post-1/amp/index.html - -7 => [ - 'name' => 'amp', - 'mediatype' => 'text/html', - 'subpath' => 'amp', - 'filename' => 'index', - 'extension' => 'html', - ], - // e.g.: sw.js - -8 => [ - 'name' => 'js', - 'mediatype' => 'application/javascript', - 'extension' => 'js', - ], - // e.g.: manifest.webmanifest - -9 => [ - 'name' => 'webmanifest', - 'mediatype' => 'application/manifest+json', - 'extension' => 'webmanifest', - ], - // e.g.: atom.xsl - -10 => [ - 'name' => 'xsl', - 'mediatype' => 'application/xml', - 'extension' => 'xsl', - ], - // e.g.: blog/feed.json - -11 => [ - 'name' => 'jsonfeed', - 'mediatype' => 'application/json', - 'filename' => 'feed', - 'extension' => 'json', - 'exclude' => ['redirect', 'paginated'], - ], - // e.g.: video/embed.html - -12 => [ - 'name' => 'iframe', - 'mediatype' => 'text/html', - 'filename' => 'embed', - 'extension' => 'html', - 'exclude' => ['redirect', 'paginated'], - ], - // e.g.: video/embed.json - -13 => [ - 'name' => 'oembed', - 'mediatype' => 'application/json+oembed', - 'filename' => 'embed', - 'extension' => 'json', - 'exclude' => ['redirect', 'paginated'], - ], - ], + 'output' => [ // output formats and post process + 'dir' => '_site', // output directory + //'formats' => [ // https://cecil.app/documentation/configuration/#formats + // [ + // 'name' => '', + // 'mediatype' => '', + // 'filename' => '', + // 'extension' => '', + // 'exclude' => ['variable1', 'variable2'], + // ], + //], 'pagetypeformats' => [ // formats applied by page type 'page' => ['html'], 'homepage' => ['html', 'atom'], @@ -380,34 +223,30 @@ 'vocabulary' => ['html'], 'term' => ['html', 'atom'], ], - 'postprocessors' => [ // list of output post processors class - 'GeneratorMetaTag' => 'Cecil\Renderer\PostProcessor\GeneratorMetaTag', - 'HtmlExcerpt' => 'Cecil\Renderer\PostProcessor\HtmlExcerpt', - 'MarkdownLink' => 'Cecil\Renderer\PostProcessor\MarkdownLink', - ], + //'postprocessors' => [ // list of output post processors class + // '' => 'Cecil\Renderer\PostProcessor\', + //], ], - // cache management - 'cache' => [ - 'enabled' => true, // enables cache support (`true` by default) - 'dir' => '.cache', // cache files directory (`.cache` by default) + 'cache' => [ // cache management + 'enabled' => true, // enables cache support + 'dir' => '.cache', // cache files directory 'templates' => [ 'enabled' => true, // enables cache for Twig templates - 'dir' => 'templates', // templates files cache directory (`templates` by default) + 'dir' => 'templates', // templates files cache directory ], 'assets' => [ - 'dir' => 'assets', // assets files cache directory (`assets` by default) + 'dir' => 'assets', // assets files cache directory 'remote' => [ - 'dir' => 'remote', // remote files cache directory (`remote` by default) + 'dir' => 'remote', // remote files cache directory ], ], 'translations' => [ 'enabled' => true, // enables cache for translations dictionary - 'dir' => 'translations', // translations files cache directory (`assets` by default) + 'dir' => 'translations', // translations files cache directory ], ], - // files optimization - 'optimize' => [ - 'enabled' => false, // enables files optimization (`false` by default) + 'optimize' => [ // files optimization + 'enabled' => false, // enables files optimization 'html' => [ 'enabled' => true, // enables HTML files optimization 'ext' => ['html', 'htm'], // supported files extensions diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index aa69985d3..57fcb22bf 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -259,7 +259,7 @@ public function compile(): self $importDir = []; $importDir[] = Util::joinPath($this->config->getStaticPath()); $importDir[] = Util::joinPath($this->config->getAssetsPath()); - $scssDir = $this->config->get('assets.compile.import') ?? []; + $scssDir = $this->config->get('assets.compile.import'); $themes = $this->config->getTheme() ?? []; foreach ($scssDir as $dir) { $importDir[] = Util::joinPath($this->config->getStaticPath(), $dir); @@ -297,7 +297,7 @@ public function compile(): self } $scssPhp->setOutputStyle($outputStyle); // variables - $variables = $this->config->get('assets.compile.variables') ?? []; + $variables = $this->config->get('assets.compile.variables'); if (!empty($variables)) { $variables = array_map('ScssPhp\ScssPhp\ValueConverter::parseValue', $variables); $scssPhp->replaceVariables($variables); @@ -384,7 +384,7 @@ public function optimize(string $filepath): self return $this; } - $quality = $this->config->get('assets.images.quality') ?? 75; + $quality = $this->config->get('assets.images.quality'); $cache = new Cache($this->builder, (string) $this->builder->getConfig()->get('cache.assets.dir')); $tags = ["q$quality", 'optimized']; if ($this->data['width']) { @@ -438,7 +438,7 @@ public function resize(int $width): self return $assetResized; // returns asset with the new width only: CDN do the rest of the job } - $quality = $this->config->get('assets.images.quality') ?? 75; + $quality = $this->config->get('assets.images.quality'); $cache = new Cache($this->builder, (string) $this->builder->getConfig()->get('cache.assets.dir')); $cacheKey = $cache->createKeyFromAsset($assetResized, ["{$width}x", "q$quality"]); if (!$cache->has($cacheKey)) { @@ -471,7 +471,7 @@ public function convert(string $format, ?int $quality = null): self } if ($quality === null) { - $quality = (int) $this->config->get('assets.images.quality') ?? 75; + $quality = (int) $this->config->get('assets.images.quality'); } $asset = clone $this; @@ -605,7 +605,7 @@ public function getVideo(): array public function dataurl(): string { if ($this->data['type'] == 'image' && !Image::isSVG($this)) { - return Image::getDataUrl($this, $this->config->get('assets.images.quality') ?? 75); + return Image::getDataUrl($this, $this->config->get('assets.images.quality')); } return \sprintf('data:%s;base64,%s', $this->data['subtype'], base64_encode($this->data['content'])); @@ -896,9 +896,9 @@ private function buildImageCdnUrl(): string ], [ $this->config->get('assets.images.cdn.account'), - ltrim($this->data['url'] ?? (string) new Url($this->builder, $this->data['path'], ['canonical' => $this->config->get('assets.images.cdn.canonical') ?? true]), '/'), + ltrim($this->data['url'] ?? (string) new Url($this->builder, $this->data['path'], ['canonical' => $this->config->get('assets.images.cdn.canonical')]), '/'), $this->data['width'], - $this->config->get('assets.images.quality') ?? 75, + $this->config->get('assets.images.quality'), $this->data['ext'], ], (string) $this->config->get('assets.images.cdn.url') diff --git a/src/Builder.php b/src/Builder.php index 5e62395e4..f812c5f38 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -134,8 +134,8 @@ public function build(array $options): self $startTime = microtime(true); $startMemory = memory_get_usage(); - // checks the configuration - $this->validConfig(); + // log configuration errors + $this->logConfigError(); // prepare options $this->options = array_merge([ @@ -403,26 +403,13 @@ public static function getVersion(): string } /** - * Checks the configuration. + * Log configuration errors. */ - protected function validConfig(): void + protected function logConfigError(): void { // baseurl if (empty(trim((string) $this->config->get('baseurl'), '/'))) { - $this->getLogger()->error('Config: `baseurl` is required in production (e.g.: "baseurl: https://example.com/").'); - } - // default language - if (!preg_match('/^' . Config::LANG_CODE_PATTERN . '$/', $this->config->getLanguageDefault())) { - throw new RuntimeException(\sprintf('Config: default language code "%s" is not valid (e.g.: "language: fr-FR").', $this->config->getLanguageDefault())); - } - // locales - foreach ($this->config->getLanguages() as $lang) { - if (!isset($lang['locale'])) { - throw new RuntimeException('Config: a language locale is not defined.'); - } - if (!preg_match('/^' . Config::LANG_LOCALE_PATTERN . '$/', $lang['locale'])) { - throw new RuntimeException(\sprintf('Config: the language locale "%s" is not valid (e.g.: "locale: fr_FR").', $lang['locale'])); - } + $this->getLogger()->error('`baseurl` configuration key is required in production (e.g.: "baseurl: https://example.com/").'); } } } diff --git a/src/Command/AbstractCommand.php b/src/Command/AbstractCommand.php index 12fe4a134..79f6d68ff 100644 --- a/src/Command/AbstractCommand.php +++ b/src/Command/AbstractCommand.php @@ -190,14 +190,14 @@ protected function getBuilder(array $config = []): Builder try { // config if ($this->config === null) { - $siteConfig = []; + $filesConfig = []; foreach ($this->getConfigFiles() as $fileName => $filePath) { if ($filePath === false || false === $configContent = Util\File::fileGetContents($filePath)) { throw new RuntimeException(\sprintf('Can\'t read configuration file "%s".', $fileName)); } - $siteConfig = array_replace_recursive($siteConfig, (array) Yaml::parse($configContent, Yaml::PARSE_DATETIME)); + $filesConfig = array_replace_recursive($filesConfig, (array) Yaml::parse($configContent, Yaml::PARSE_DATETIME)); } - $this->config = array_replace_recursive($siteConfig, $config); + $this->config = array_replace_recursive($filesConfig, $config); } // builder if ($this->builder === null) { diff --git a/src/Config.php b/src/Config.php index 8149329cb..70da88e60 100644 --- a/src/Config.php +++ b/src/Config.php @@ -14,7 +14,6 @@ namespace Cecil; use Cecil\Exception\ConfigException; -use Cecil\Exception\RuntimeException; use Cecil\Util\Plateform; use Dflydev\DotAccessData\Data; @@ -23,21 +22,24 @@ */ class Config { - /** @var Data Configuration is a Data object. */ - protected $data; + /** Configuration is a Data object. */ + protected Data $data; - /** @var array Configuration. */ - protected $siteConfig; + /** Default configuration is a Data object. */ + protected Data $default; - /** @var string Source directory. */ - protected $sourceDir; + /** Source directory. */ + protected string $sourceDir; - /** @var string Destination directory. */ - protected $destinationDir; + /** Destination directory. */ + protected string $destinationDir; - /** @var array Languages. */ - protected $languages = null; + /** Languages list as array. */ + protected ?array $languages = null; + public const PRESERVE = 0; + public const REPLACE = 1; + public const MERGE = 2; public const LANG_CODE_PATTERN = '([a-z]{2}(-[A-Z]{2})?)'; // "fr" or "fr-FR" public const LANG_LOCALE_PATTERN = '[a-z]{2}(_[A-Z]{2})?(_[A-Z]{2})?'; // "fr" or "fr_FR" or "no_NO_NY" @@ -46,85 +48,32 @@ class Config */ public function __construct(?array $config = null) { - // load default configuration - $defaultConfig = realpath(Util::joinFile(__DIR__, '..', 'config/default.php')); + // default configuration + $defaultConfigFile = realpath(Util::joinFile(__DIR__, '..', 'config/default.php')); if (Plateform::isPhar()) { - $defaultConfig = Util::joinPath(Plateform::getPharPath(), 'config/default.php'); + $defaultConfigFile = Util::joinPath(Plateform::getPharPath(), 'config/default.php'); } - $this->data = new Data(include $defaultConfig); + $this->default = new Data(include $defaultConfigFile); - // import site config - $this->siteConfig = $config; - $this->importSiteConfig(); - } - - /** - * Imports site configuration. - */ - private function importSiteConfig(): void - { - $this->data->import($this->siteConfig, Data::REPLACE); - - /** - * Overrides configuration with environment variables. - */ - $data = $this->getData(); - $applyEnv = function ($array) use ($data) { - $iterator = new \RecursiveIteratorIterator( - new \RecursiveArrayIterator($array), - \RecursiveIteratorIterator::SELF_FIRST - ); - $iterator->rewind(); - while ($iterator->valid()) { - $path = []; - foreach (range(0, $iterator->getDepth()) as $depth) { - $path[] = $iterator->getSubIterator($depth)->key(); - } - $sPath = implode('_', $path); - if ($getEnv = getenv('CECIL_' . strtoupper($sPath))) { - $data->set(str_replace('_', '.', strtolower($sPath)), $this->castSetValue($getEnv)); - } - $iterator->next(); - } - }; - $applyEnv($data->export()); - } - - /** - * Casts boolean value given to set() as string. - * - * @param mixed $value - * - * @return bool|mixed - */ - private function castSetValue($value) - { - if (\is_string($value)) { - switch ($value) { - case 'true': - return true; - case 'false': - return false; - default: - return $value; - } + // base configuration + $baseConfigFile = realpath(Util::joinFile(__DIR__, '..', 'config/base.php')); + if (Plateform::isPhar()) { + $baseConfigFile = Util::joinPath(Plateform::getPharPath(), 'config/base.php'); } + $this->data = new Data(include $baseConfigFile); - return $value; + // import config + $this->import($config ?? []); } /** - * Imports (theme) configuration. + * Imports (and validate) configuration. */ - public function import(array $config): void + public function import(array $config, $mode = self::MERGE): void { - $this->data->import($config, Data::REPLACE); - - // re-import site config - $this->importSiteConfig(); - - // checks the configuration - $this->valid(); + $this->data->import($config, $mode); + $this->validate(); + $this->override(); } /** @@ -137,23 +86,45 @@ public function getAsArray(): array /** * Is configuration's key exists? + * + * @param string $key Configuration key + * @param string $language Language code (optional) + * @param bool $fallback Set to false to not return the value in the default language as fallback */ - public function has(string $key): bool + public function has(string $key, ?string $language = null, bool $fallback = true): bool { - return $this->data->has($key); + $default = $this->default->has($key); + + if ($language !== null) { + $langIndex = $this->getLanguageIndex($language); + $keyLang = "languages.$langIndex.config.$key"; + if ($this->data->has($keyLang)) { + return true; + } + if ($language !== $this->getLanguageDefault() && $fallback === false) { + return $default; + } + } + if ($this->data->has($key)) { + return true; + } + + return $default; } /** * Get the value of a configuration's key. * * @param string $key Configuration key - * @param string $language Language code (optionnal) + * @param string $language Language code (optional) * @param bool $fallback Set to false to not return the value in the default language as fallback * * @return mixed|null */ public function get(string $key, ?string $language = null, bool $fallback = true) { + $default = $this->default->has($key) ? $this->default->get($key) : null; + if ($language !== null) { $langIndex = $this->getLanguageIndex($language); $keyLang = "languages.$langIndex.config.$key"; @@ -161,14 +132,14 @@ public function get(string $key, ?string $language = null, bool $fallback = true return $this->data->get($keyLang); } if ($language !== $this->getLanguageDefault() && $fallback === false) { - return null; + return $default; } } if ($this->data->has($key)) { return $this->data->get($key); } - return null; + return $default; } /** @@ -328,12 +299,12 @@ public function getAssetsPath(): string /** * Returns cache path. * - * @throws RuntimeException + * @throws ConfigException */ public function getCachePath(): string { if (empty((string) $this->get('cache.dir'))) { - throw new RuntimeException(\sprintf('The cache directory ("%s") is not defined in configuration.', 'cache.dir')); + throw new ConfigException(\sprintf('The cache directory ("%s") is not defined.', 'cache.dir')); } if ($this->isCacheDirIsAbsolute()) { @@ -385,14 +356,14 @@ public function getCacheAssetsRemotePath(): string /** * Returns the property value of an output format. * - * @throws RuntimeException + * @throws ConfigException */ public function getOutputFormatProperty(string $name, string $property): string|array|null { $properties = array_column((array) $this->get('output.formats'), $property, 'name'); if (empty($properties)) { - throw new RuntimeException(\sprintf('Property "%s" is not defined for format "%s".', $property, $name)); + throw new ConfigException(\sprintf('Property "%s" is not defined for format "%s".', $property, $name)); } return $properties[$name] ?? null; @@ -407,7 +378,7 @@ public function getOutputFormatProperty(string $name, string $property): string| */ public function getAssetsImagesWidths(): array { - return \count((array) $this->get('assets.images.responsive.widths')) > 0 ? (array) $this->get('assets.images.responsive.widths') : [480, 640, 768, 1024, 1366, 1600, 1920]; + return $this->get('assets.images.responsive.widths'); } /** @@ -415,7 +386,7 @@ public function getAssetsImagesWidths(): array */ public function getAssetsImagesSizes(): array { - return \count((array) $this->get('assets.images.responsive.sizes')) > 0 ? (array) $this->get('assets.images.responsive.sizes') : ['default' => '100vw']; + return $this->get('assets.images.responsive.sizes'); } /* @@ -441,14 +412,14 @@ public function getTheme(): ?array /** * Has a (valid) theme(s)? * - * @throws RuntimeException + * @throws ConfigException */ public function hasTheme(): bool { if ($themes = $this->getTheme()) { foreach ($themes as $theme) { if (!Util\File::getFS()->exists($this->getThemeDirPath($theme, 'layouts')) && !Util\File::getFS()->exists(Util::joinFile($this->getThemesPath(), $theme, 'config.yml'))) { - throw new RuntimeException(\sprintf('Theme "%s" not found. Did you forgot to install it?', $theme)); + throw new ConfigException(\sprintf('Theme "%s" not found. Did you forgot to install it?', $theme)); } } @@ -474,7 +445,7 @@ public function getThemeDirPath(string $theme, string $dir = 'layouts'): string /** * Returns an array of available languages. * - * @throws RuntimeException + * @throws ConfigException */ public function getLanguages(): array { @@ -487,7 +458,7 @@ public function getLanguages(): array }); if (!\is_int(array_search($this->getLanguageDefault(), array_column($languages, 'code')))) { - throw new RuntimeException(\sprintf('The default language "%s" is not listed in "languages" key configuration.', $this->getLanguageDefault())); + throw new ConfigException(\sprintf('The default language "%s" is not listed in "languages".', $this->getLanguageDefault())); } $this->languages = $languages; @@ -498,16 +469,16 @@ public function getLanguages(): array /** * Returns the default language code (ie: "en", "fr-FR", etc.). * - * @throws RuntimeException + * @throws ConfigException */ public function getLanguageDefault(): string { if (!$this->get('language')) { - throw new RuntimeException('There is no default "language" key in configuration.'); + throw new ConfigException('There is no default "language" key.'); } if (\is_array($this->get('language'))) { if (!$this->get('language.code')) { - throw new RuntimeException('There is no "language.code" key in configuration.'); + throw new ConfigException('There is no "language.code" key.'); } return $this->get('language.code'); @@ -519,14 +490,14 @@ public function getLanguageDefault(): string /** * Returns a language code index. * - * @throws RuntimeException + * @throws ConfigException */ public function getLanguageIndex(string $code): int { $array = array_column($this->getLanguages(), 'code'); if (false === $index = array_search($code, $array)) { - throw new RuntimeException(\sprintf('The language code "%s" is not defined.', $code)); + throw new ConfigException(\sprintf('The language code "%s" is not defined.', $code)); } return $index; @@ -535,7 +506,7 @@ public function getLanguageIndex(string $code): int /** * Returns the property value of a (specified or the default) language. * - * @throws RuntimeException + * @throws ConfigException */ public function getLanguageProperty(string $property, ?string $code = null): string { @@ -544,7 +515,7 @@ public function getLanguageProperty(string $property, ?string $code = null): str $properties = array_column($this->getLanguages(), $property, 'code'); if (empty($properties)) { - throw new RuntimeException(\sprintf('Property "%s" is not defined for language "%s".', $property, $code)); + throw new ConfigException(\sprintf('Property "%s" is not defined for language "%s".', $property, $code)); } return $properties[$code]; @@ -569,35 +540,62 @@ public function isCacheDirIsAbsolute(): bool } /** - * Set a Data object as configuration. + * Overrides configuration with environment variables. */ - protected function setData(Data $data): self + private function override(): void { - if ($this->data !== $data) { - $this->data = $data; - } - - return $this; + $data = $this->data; + $applyEnv = function ($array) use ($data) { + $iterator = new \RecursiveIteratorIterator( + new \RecursiveArrayIterator($array), + \RecursiveIteratorIterator::SELF_FIRST + ); + $iterator->rewind(); + while ($iterator->valid()) { + $path = []; + foreach (range(0, $iterator->getDepth()) as $depth) { + $path[] = $iterator->getSubIterator($depth)->key(); + } + $sPath = implode('_', $path); + if ($getEnv = getenv('CECIL_' . strtoupper($sPath))) { + $data->set(str_replace('_', '.', strtolower($sPath)), $this->castSetValue($getEnv)); + } + $iterator->next(); + } + }; + $applyEnv($data->export()); } /** - * Get configuration as a Data object. + * Casts boolean value given to set() as string. + * + * @param mixed $value + * + * @return bool|mixed */ - protected function getData(): Data + private function castSetValue($value) { - return $this->data; + $filteredValue = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + + if ($filteredValue !== null) { + return $filteredValue; + } + + return $value; } /** - * Valid the configuration. + * Validate the configuration. + * + * @throws ConfigException */ - private function valid(): void + private function validate(): void { // default language must be valid if (!preg_match('/^' . Config::LANG_CODE_PATTERN . '$/', $this->getLanguageDefault())) { throw new ConfigException(\sprintf('Default language code "%s" is not valid (e.g.: "language: fr-FR").', $this->getLanguageDefault())); } - // if language is set then the locale is required + // if language is set then the locale is required and must be valid foreach ((array) $this->get('languages') as $lang) { if (!isset($lang['locale'])) { throw new ConfigException('A language locale is not defined.'); @@ -606,7 +604,8 @@ private function valid(): void throw new ConfigException(\sprintf('The language locale "%s" is not valid (e.g.: "locale: fr_FR").', $lang['locale'])); } } - // Version 8.x breaking changes + + // version 8.x breaking changes detection $toV8 = [ 'frontmatter' => 'pages:frontmatter', 'body' => 'pages:body', diff --git a/src/Converter/Parsedown.php b/src/Converter/Parsedown.php index c651f7c17..4ea851dfd 100644 --- a/src/Converter/Parsedown.php +++ b/src/Converter/Parsedown.php @@ -111,19 +111,19 @@ protected function inlineLink($Excerpt) // External link if (str_starts_with($link['element']['attributes']['href'], 'http') && !str_starts_with($link['element']['attributes']['href'], (string) $this->config->get('baseurl'))) { - if ($this->config->get('pages.body.links.external.blank') ?? false) { + if ($this->config->get('pages.body.links.external.blank')) { $link['element']['attributes']['target'] = '_blank'; } if (!\array_key_exists('rel', $link['element']['attributes'])) { $link['element']['attributes']['rel'] = ''; } - if ($this->config->get('pages.body.links.external.noopener') ?? true) { + if ($this->config->get('pages.body.links.external.noopener')) { $link['element']['attributes']['rel'] .= ' noopener'; } - if ($this->config->get('pages.body.links.external.noreferrer') ?? true) { + if ($this->config->get('pages.body.links.external.noreferrer')) { $link['element']['attributes']['rel'] .= ' noreferrer'; } - if ($this->config->get('pages.body.links.external.nofollow') ?? true) { + if ($this->config->get('pages.body.links.external.nofollow')) { $link['element']['attributes']['rel'] .= ' nofollow'; } $link['element']['attributes']['rel'] = trim($link['element']['attributes']['rel']); @@ -133,7 +133,7 @@ protected function inlineLink($Excerpt) * Embed link? */ $embed = false; - $embed = (bool) $this->config->get('pages.body.links.embed.enabled') ?? false; + $embed = (bool) $this->config->get('pages.body.links.embed.enabled'); if (isset($link['element']['attributes']['embed'])) { $embed = true; if ($link['element']['attributes']['embed'] == 'false') { @@ -268,7 +268,7 @@ protected function inlineImage($Excerpt) } // disable remote image handling? - if (Util\Url::isUrl($InlineImage['element']['attributes']['src']) && !(bool) $this->config->get('pages.body.images.remote.enabled') ?? true) { + if (Util\Url::isUrl($InlineImage['element']['attributes']['src']) && !(bool) $this->config->get('pages.body.images.remote.enabled')) { return $InlineImage; } @@ -296,6 +296,7 @@ protected function inlineImage($Excerpt) } if ( (bool) $this->config->get('pages.body.images.responsive.enabled') + && !empty($this->config->getAssetsImagesWidths()) && $width > max($this->config->getAssetsImagesWidths()) ) { $shouldResize = true; @@ -388,7 +389,7 @@ protected function inlineImage($Excerpt) // converts image to formats and put them in picture > source if ( - \count($formats = ((array) $this->config->get('pages.body.images.formats') ?? [])) > 0 + \count($formats = ((array) $this->config->get('pages.body.images.formats'))) > 0 && \in_array($InlineImage['element']['attributes']['src']['subtype'], ['image/jpeg', 'image/png', 'image/gif']) ) { try { diff --git a/src/Generator/Pagination.php b/src/Generator/Pagination.php index b1863160d..2bcfb7c9b 100644 --- a/src/Generator/Pagination.php +++ b/src/Generator/Pagination.php @@ -50,8 +50,8 @@ public function generate(): void } $path = $page->getPath(); // site pagination configuration - $paginationPerPage = \intval($this->config->get('pagination.max') ?? 5); - $paginationPath = (string) $this->config->get('pagination.path') ?? 'page'; + $paginationPerPage = \intval($this->config->get('pagination.max')); + $paginationPath = (string) $this->config->get('pagination.path'); // page pagination configuration $pagePagination = $page->getVariable('pagination'); if ($pagePagination) { diff --git a/src/Renderer/Extension/Core.php b/src/Renderer/Extension/Core.php index 5fbad8412..c56367eff 100644 --- a/src/Renderer/Extension/Core.php +++ b/src/Renderer/Extension/Core.php @@ -451,7 +451,7 @@ public function scssToCss(?string $value): string throw new ConfigException(\sprintf('"%s" value must be "%s".', 'assets.compile.style', implode('" or "', $outputStyles))); } $scssPhp->setOutputStyle($outputStyle); - $variables = $this->config->get('assets.compile.variables') ?? []; + $variables = $this->config->get('assets.compile.variables'); if (!empty($variables)) { $variables = array_map('ScssPhp\ScssPhp\ValueConverter::parseValue', $variables); $scssPhp->replaceVariables($variables); @@ -478,8 +478,8 @@ public function html(array $context, Asset $asset, array $attributes = [], array { $htmlAttributes = ''; $preload = false; - $responsive = (bool) $this->config->get('assets.images.responsive.enabled') ?? false; - $formats = (array) $this->config->get('assets.images.formats') ?? []; + $responsive = (bool) $this->config->get('assets.images.responsive.enabled'); + $formats = (array) $this->config->get('assets.images.formats'); extract($options, EXTR_IF_EXISTS); // builds HTML attributes diff --git a/src/Renderer/Twig.php b/src/Renderer/Twig.php index 5431894f7..d06b28ae4 100644 --- a/src/Renderer/Twig.php +++ b/src/Renderer/Twig.php @@ -116,7 +116,7 @@ public function __construct(Builder $builder, $templatesPath) switch ($name) { case 'localizeddate': return new \Twig\TwigFilter($name, function (?\DateTime $value = null) { - return date($this->builder->getConfig()->get('date.format') ?? 'F j, Y', $value->getTimestamp()); + return date($this->builder->getConfig()->get('date.format'), $value->getTimestamp()); }); } diff --git a/src/Step/Optimize/Images.php b/src/Step/Optimize/Images.php index c54446005..7c747a4e7 100644 --- a/src/Step/Optimize/Images.php +++ b/src/Step/Optimize/Images.php @@ -42,7 +42,7 @@ public function init(array $options): void */ public function setProcessor(): void { - $this->processor = Optimizer::create($this->config->get('assets.images.quality') ?? 75); + $this->processor = Optimizer::create($this->config->get('assets.images.quality')); } /** diff --git a/src/Step/Pages/Convert.php b/src/Step/Pages/Convert.php index 6ae2e732f..cb4a9d19f 100644 --- a/src/Step/Pages/Convert.php +++ b/src/Step/Pages/Convert.php @@ -105,7 +105,7 @@ public function process(): void */ public function convertPage(Builder $builder, Page $page, ?string $format = null, ?ConverterInterface $converter = null): Page { - $format = $format ?? (string) $builder->getConfig()->get('pages.frontmatter.format') ?? 'yaml'; + $format = $format ?? (string) $builder->getConfig()->get('pages.frontmatter.format'); $converter = $converter ?? new Converter($builder); // converts front matter diff --git a/src/Step/Pages/Render.php b/src/Step/Pages/Render.php index 4bc73cf5b..74150d75d 100644 --- a/src/Step/Pages/Render.php +++ b/src/Step/Pages/Render.php @@ -264,7 +264,7 @@ protected function getOutputFormats(Page $page): array $formats = [$formats]; } - return $formats; + return array_unique($formats); } /** diff --git a/src/Step/Themes/Import.php b/src/Step/Themes/Import.php index 17f091d7b..7728baea9 100644 --- a/src/Step/Themes/Import.php +++ b/src/Step/Themes/Import.php @@ -13,6 +13,7 @@ namespace Cecil\Step\Themes; +use Cecil\Config; use Cecil\Exception\RuntimeException; use Cecil\Step\AbstractStep; use Cecil\Util; @@ -50,7 +51,7 @@ public function init(array $options): void */ public function process(): void { - $themes = array_reverse((array) $this->config->getTheme()); + $themes = (array) $this->config->getTheme(); $count = 0; $total = \count($themes); foreach ($themes as $theme) { @@ -62,7 +63,7 @@ public function process(): void throw new RuntimeException('Can\'t read the configuration file.'); } $themeConfig = Yaml::parse($config, Yaml::PARSE_DATETIME); - $this->config->import($themeConfig); + $this->config->import($themeConfig, Config::PRESERVE); $message = \sprintf('Theme "%s" imported', $theme); }