config = $config; $this->themeDir = $config['theme_dir'] ?? (__DIR__ . '/../../../themes/' . ($config['active_theme'] ?? 'default')); $this->themeConfig = $config['theme'] ?? []; $loader = new FilesystemLoader($this->themeDir); $this->twig = new Environment($loader, [ 'cache' => false, 'autoescape' => false, ]); } /** * Geeft het absolute pad van de actieve thema-map. * * @since 2.6.4 * * @return string Absoluut pad naar de thema-map. */ public function getThemeDir(): string { return $this->themeDir; } /** * Geeft de ruwe theme.json configuratie-array. * * @since 2.6.4 * * @return array Theme.json inhoud, of lege array bij afwezigheid. */ public function getThemeConfig(): array { return $this->themeConfig; } /** * Geeft de thema-titel. * * Leest eerst 'title' uit theme.json, dan 'name', en als laatste * fallback de mapnaam van het thema. * * @since 2.6.4 * * @return string Thematische titel. */ public function getTitle(): string { return $this->themeConfig['title'] ?? $this->themeConfig['name'] ?? basename($this->themeDir); } /** * Geeft de 'config'-sectie van theme.json. * * Bevat o.a. default_template en achtergrondinstellingen. * * @since 2.6.4 * * @return array Config-sectie, of lege array bij afwezigheid. */ public function getConfig(): array { return $this->themeConfig['config'] ?? []; } /** * Geeft de 'template'-sectie van theme.json. * * Bevat de mapping van layout-key naar .twig-bestand. * * @since 2.6.4 * * @return array Layout-key => .twig-bestand mapping. */ public function getTemplates(): array { return $this->themeConfig['template'] ?? []; } /** * Bepaalt het .twig-templatebestand voor een aangevraagde layout. * * Templates worden gedefinieerd in theme.json onder de "template"-sectie: * { "template": { "full_content": "full_content.twig", ... } } * De standaardtemplate staat in de "config"-sectie: * { "config": { "default_template": "full_content", ... } } * * Prioriteit: * 1. Als de layout een bekende key is in de "template"-sectie, gebruik * het bijbehorende bestand. * 2. Anders fallback naar config.default_template. * 3. Laatste veiligheidsnet: full_content.twig. * * @since 2.6.4 * * @param string $layout Aangevraagde layout-key (bijv. 'left_sidebar'). * @return string Templacenaam bruikbaar voor de Twig-loader. */ public function getTemplateForLayout(string $layout): string { $layout = trim($layout); $templates = $this->getTemplates(); if ($layout !== '' && isset($templates[$layout])) { $file = $templates[$layout]; if ($this->templateExists($file)) { return $file; } } $config = $this->getConfig(); $default = $config['default_template'] ?? 'full_content'; if (isset($templates[$default])) { $file = $templates[$default]; if ($this->templateExists($file)) { return $file; } } return 'full_content.twig'; } /** * Geeft de lijst met beschikbare layout-keys uit de "template"-sectie. * * @since 2.6.4 * * @return array Lijst met layout-keys. */ public function getLayouts(): array { return array_keys($this->getTemplates()); } /** * Controleert of een templatebestand bestaat in de thema-map. * * @since 2.6.4 * * @param string $file Bestandsnaam van het template (relatief t.o.v. thema-map). * @return bool True als het bestand bestaat. */ private function templateExists(string $file): bool { $path = $this->themeDir . '/' . ltrim($file, '/'); return is_file($path); } /** * Rendert een layout-template met de gegeven data. * * @since 2.6.4 * * @param string $layout Aangevraagde layout-key. * @param array $data Template-variabelen. * @return string Gerenderde HTML. */ public function render(string $layout, array $data): string { $template = $this->getTemplateForLayout($layout); return $this->twig->render($template, $data); } /** * Compileert de SCSS van het thema naar CSS (gecacht op bron-mtime). * * Compileert vanuit assets/scss/theme.scss naar assets/css_compiled/theme.css. * De gecompileerde CSS en een .mtime-cachebestand worden op alleen-lezen * (0444) gezet om handmatige aanpassingen te voorkomen. Bij een fout * wordt deze gelogd en null teruggegeven. * * @since 2.6.4 * * @param bool $force Forceer hercompilatie ongeacht de cache. Default false. * @return string|null Absoluut pad naar de gecompileerde CSS, of null bij fout/afwezigheid. */ public function compileCss(bool $force = false): ?string { $scssFile = $this->themeDir . '/assets/scss/theme.scss'; if (!is_file($scssFile)) { return null; } $outDir = $this->themeDir . '/assets/css_compiled'; $outFile = $outDir . '/theme.css'; $cacheFile = $outDir . '/.mtime'; $mtime = filemtime($scssFile); if (!$force && is_file($outFile) && is_file($cacheFile) && (int)file_get_contents($cacheFile) === $mtime) { return $outFile; } if (!is_dir($outDir)) { mkdir($outDir, 0755, true); } try { $compiler = new Compiler(); $compiler->setImportPaths($this->themeDir . '/assets/scss'); $css = $compiler->compileString(file_get_contents($scssFile))->getCss(); // Remove read-only before writing (file may be locked from previous compile) if (is_file($outFile)) { @chmod($outFile, 0644); } if (is_file($cacheFile)) { @chmod($cacheFile, 0644); } file_put_contents($outFile, $css); file_put_contents($cacheFile, (string)$mtime); // Set read-only to prevent manual edits @chmod($outFile, 0444); @chmod($cacheFile, 0444); return $outFile; } catch (\Throwable $e) { error_log('ThemeManager SCSS compile error: ' . $e->getMessage()); return null; } } /** * Geeft de publieke URL voor de thema-CSS. * * Gebruikt assets/css_compiled/theme.css (gecompileerd vanuit SCSS door * scssphp). Indien de gecompileerde CSS nog niet bestaat, wordt deze * alsnog gecompileerd. * * @since 2.6.4 * * @return string|null Publieke CSS-URL, of null indien niet beschikbaar. */ public function getCssUrl(): ?string { $compiledCss = $this->themeDir . '/assets/css_compiled/theme.css'; if (is_file($compiledCss)) { return $this->getThemeAssetUrl('css_compiled/theme.css'); } $compiled = $this->compileCss(); if ($compiled !== null) { return $this->getThemeAssetUrl('css_compiled/theme.css'); } return null; } /** * Geeft de publieke URL voor de thema-JS, of null indien niet beschikbaar. * * @since 2.6.4 * * @return string|null Publieke JS-URL, of null indien niet beschikbaar. */ public function getJsUrl(): ?string { $jsFile = $this->themeDir . '/assets/js/theme.js'; if (!is_file($jsFile)) { return null; } return $this->getThemeAssetUrl('js/theme.js'); } /** * Geeft de publieke URL voor een thema-asset. * * @since 2.6.4 * * @param string $assetPath Pad naar de asset binnen de assets/-map van het thema. * @return string Publieke URL in de vorm '/themes//assets/'. */ private function getThemeAssetUrl(string $assetPath): string { $themeName = basename($this->themeDir); return '/themes/' . $themeName . '/assets/' . $assetPath; } /** * Controleert of het thema een SCSS-bronbestand heeft. * * @since 2.6.4 * * @return bool True als assets/scss/theme.scss bestaat. */ public function hasScss(): bool { return is_file($this->themeDir . '/assets/scss/theme.scss'); } /** * Controleert of de gecompileerde CSS nieuwer is dan de SCSS-bron. * * @since 2.6.4 * * @return bool True als de CSS-_mtime >= SCSS-mtime, anders false. */ public function isScssCompiled(): bool { $scssFile = $this->themeDir . '/assets/scss/theme.scss'; $cssFile = $this->themeDir . '/assets/css_compiled/theme.css'; if (!is_file($scssFile) || !is_file($cssFile)) { return false; } return filemtime($cssFile) >= filemtime($scssFile); } /** * Controleert of het thema een handmatig CSS-bestand heeft. * * @since 2.6.4 * * @return bool True als assets/css/theme.css bestaat. */ public function hasManualCss(): bool { return is_file($this->themeDir . '/assets/css/theme.css'); } /** * Geeft een lijst met CSS-bestanden in de assets/css/-map van het thema. * * De css_compiled-map wordt uitgesloten. * * @since 2.6.4 * * @return array Lijst met publieke CSS-URLs. */ public function getCssFiles(): array { $cssDir = $this->themeDir . '/assets/css'; $files = []; if (!is_dir($cssDir)) { return $files; } foreach (scandir($cssDir) as $file) { if ($file[0] === '.') continue; if (pathinfo($file, PATHINFO_EXTENSION) === 'css') { $files[] = $this->getThemeAssetUrl('css/' . $file); } } return $files; } /** * Geeft een lijst met JS-bestanden in de assets/js/-map van het thema. * * @since 2.6.4 * * @return array Lijst met publieke JS-URLs. */ public function getJsFiles(): array { $jsDir = $this->themeDir . '/assets/js'; $files = []; if (!is_dir($jsDir)) { return $files; } foreach (scandir($jsDir) as $file) { if ($file[0] === '.') continue; if (pathinfo($file, PATHINFO_EXTENSION) === 'js') { $files[] = $this->getThemeAssetUrl('js/' . $file); } } return $files; } /** * Geeft de URL voor de favicon indien deze bestaat. * * @since 2.6.4 * * @return string|null Publieke favicon-URL, of null indien niet beschikbaar. */ public function getFaviconUrl(): ?string { $faviconFile = $this->themeDir . '/assets/img/favicon.svg'; if (!is_file($faviconFile)) { return null; } return $this->getThemeAssetUrl('img/favicon.svg'); } }