CodePress/vendor/league/config/src/Configuration.php
Edwin Noorlander 863661612a ## Major CodePress CMS Enhancement
### 🚀 New Features
- **League CommonMark Integration**: Replaced basic Markdown parser with full CommonMark 2.7 support
- **Bootstrap Sass Architecture**: Modern SCSS build system with Bootstrap 5.3.8
- **Enhanced Navigation**: Uses filenames instead of H1 titles for consistency
- **Improved Styling**: Transparent navigation backgrounds, no rounded corners

### 🎨 UI/UX Improvements
- Navigation items now use formatted filenames (e.g., "kennis-boven-aantallen" → "Kennis Boven Aantallen")
- Transparent navigation backgrounds with subtle hover effects
- Removed rounded corners from first-level navigation
- 50% opacity navigation background using Bootstrap variables

### 🔧 Technical Improvements
- **Class Organization**: Extracted CodePressCMS and SimpleTemplate to separate files
- **Full PHPDoc Documentation**: Complete documentation for all methods
- **Modern Build Process**: npm scripts for SCSS compilation
- **Enhanced Markdown Support**: Tables, strikethrough, task lists, autolinks
- **Security**: Proper HTML sanitization with CommonMark

### 📦 Dependencies
- Added `league/commonmark` for professional Markdown parsing
- Added `bootstrap` for SCSS-based styling
- Updated `sass` build process

### 🐛 Bug Fixes
- Fixed content directory path configuration
- Resolved navigation title inconsistencies
- Improved Markdown bold/italic formatting
- Fixed homepage 404 issues

### 🔄 Migration Notes
- Content directory moved from `content/` to `public/content/`
- Navigation now displays filenames instead of content H1 titles
- CSS now compiled from SCSS source files

The CMS now provides a professional, modern experience with robust Markdown support and clean, maintainable code architecture.
2025-11-21 20:23:20 +01:00

256 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use Dflydev\DotAccessData\Data;
use Dflydev\DotAccessData\DataInterface;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
use Dflydev\DotAccessData\Exception\MissingPathException;
use League\Config\Exception\UnknownOptionException;
use League\Config\Exception\ValidationException;
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Nette\Schema\ValidationException as NetteValidationException;
final class Configuration implements ConfigurationBuilderInterface, ConfigurationInterface
{
/** @psalm-readonly */
private Data $userConfig;
/**
* @var array<string, Schema>
*
* @psalm-allow-private-mutation
*/
private array $configSchemas = [];
/** @psalm-allow-private-mutation */
private Data $finalConfig;
/**
* @var array<string, mixed>
*
* @psalm-allow-private-mutation
*/
private array $cache = [];
/** @psalm-readonly */
private ConfigurationInterface $reader;
/**
* @param array<string, Schema> $baseSchemas
*/
public function __construct(array $baseSchemas = [])
{
$this->configSchemas = $baseSchemas;
$this->userConfig = new Data();
$this->finalConfig = new Data();
$this->reader = new ReadOnlyConfiguration($this);
}
/**
* Registers a new configuration schema at the given top-level key
*
* @psalm-allow-private-mutation
*/
public function addSchema(string $key, Schema $schema): void
{
$this->invalidate();
$this->configSchemas[$key] = $schema;
}
/**
* {@inheritDoc}
*
* @psalm-allow-private-mutation
*/
public function merge(array $config = []): void
{
$this->invalidate();
$this->userConfig->import($config, DataInterface::REPLACE);
}
/**
* {@inheritDoc}
*
* @psalm-allow-private-mutation
*/
public function set(string $key, $value): void
{
$this->invalidate();
try {
$this->userConfig->set($key, $value);
} catch (DataException $ex) {
throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
}
}
/**
* {@inheritDoc}
*
* @psalm-external-mutation-free
*/
public function get(string $key)
{
if (\array_key_exists($key, $this->cache)) {
return $this->cache[$key];
}
try {
$this->build(self::getTopLevelKey($key));
return $this->cache[$key] = $this->finalConfig->get($key);
} catch (InvalidPathException | MissingPathException $ex) {
throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
}
}
/**
* {@inheritDoc}
*
* @psalm-external-mutation-free
*/
public function exists(string $key): bool
{
if (\array_key_exists($key, $this->cache)) {
return true;
}
try {
$this->build(self::getTopLevelKey($key));
return $this->finalConfig->has($key);
} catch (InvalidPathException | UnknownOptionException $ex) {
return false;
}
}
/**
* @psalm-mutation-free
*/
public function reader(): ConfigurationInterface
{
return $this->reader;
}
/**
* @psalm-external-mutation-free
*/
private function invalidate(): void
{
$this->cache = [];
$this->finalConfig = new Data();
}
/**
* Applies the schema against the configuration to return the final configuration
*
* @throws ValidationException|UnknownOptionException|InvalidPathException
*
* @psalm-allow-private-mutation
*/
private function build(string $topLevelKey): void
{
if ($this->finalConfig->has($topLevelKey)) {
return;
}
if (! isset($this->configSchemas[$topLevelKey])) {
throw new UnknownOptionException(\sprintf('Missing config schema for "%s"', $topLevelKey), $topLevelKey);
}
try {
$userData = [$topLevelKey => $this->userConfig->get($topLevelKey)];
} catch (DataException $ex) {
$userData = [];
}
try {
$schema = $this->configSchemas[$topLevelKey];
$processor = new Processor();
$processed = $processor->process(Expect::structure([$topLevelKey => $schema]), $userData);
$this->raiseAnyDeprecationNotices($processor->getWarnings());
$this->finalConfig->import((array) self::convertStdClassesToArrays($processed));
} catch (NetteValidationException $ex) {
throw new ValidationException($ex);
}
}
/**
* Recursively converts stdClass instances to arrays
*
* @phpstan-template T
*
* @param T $data
*
* @return mixed
*
* @phpstan-return ($data is \stdClass ? array<string, mixed> : T)
*
* @psalm-pure
*/
private static function convertStdClassesToArrays($data)
{
if ($data instanceof \stdClass) {
$data = (array) $data;
}
if (\is_array($data)) {
foreach ($data as $k => $v) {
$data[$k] = self::convertStdClassesToArrays($v);
}
}
return $data;
}
/**
* @param string[] $warnings
*/
private function raiseAnyDeprecationNotices(array $warnings): void
{
foreach ($warnings as $warning) {
@\trigger_error($warning, \E_USER_DEPRECATED);
}
}
/**
* @throws InvalidPathException
*/
private static function getTopLevelKey(string $path): string
{
if (\strlen($path) === 0) {
throw new InvalidPathException('Path cannot be an empty string');
}
$path = \str_replace(['.', '/'], '.', $path);
$firstDelimiter = \strpos($path, '.');
if ($firstDelimiter === false) {
return $path;
}
return \substr($path, 0, $firstDelimiter);
}
}