Files
2026-09-01 10:46:29 -03:00

352 lines
12 KiB
PHP
Executable File

<?php
namespace Kernel;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFunction;
class View
{
protected Environment $twig;
private static ?\Twig\Loader\LoaderInterface $loader = null;
private static ?\Twig\Environment $environment = null;
private static ?string $activeTheme = null;
private static ?string $parentTheme = null;
public function __construct(?string $viewsPath = null, array $globals = [])
{
if (self::$environment) {
$this->twig = self::$environment;
} else {
$this->initEngine($viewsPath);
}
// Registers global variables
foreach ($globals as $key => $value) {
$this->twig->addGlobal($key, $value);
}
}
protected function initEngine(?string $viewsPath = null): void
{
$baseDir = dirname(__DIR__);
$templatesDir = $baseDir . '/templates';
$modulesDir = $baseDir . '/modules';
$themesDir = $baseDir . '/themes';
// Load active theme from database if not already loaded
if (self::$activeTheme === null) {
try {
$themeConfig = \Illuminate\Database\Capsule\Manager::table('system_config')
->where('system_config_key', 'active_theme')
->first();
self::$activeTheme = $themeConfig ? $themeConfig->system_config_value : 'default';
} catch (\Exception $e) {
self::$activeTheme = 'default';
}
// Check for parent theme in style.css
$themePath = $themesDir . '/' . self::$activeTheme;
$stylePath = $themePath . '/style.css';
if (file_exists($stylePath)) {
$content = file_get_contents($stylePath);
if (preg_match('/Template:\s*(.*)$/mi', $content, $matches)) {
self::$parentTheme = trim($matches[1]);
}
}
}
$paths = [];
// 1. Priority: Active Theme
$themePath = $themesDir . '/' . self::$activeTheme;
if (is_dir($themePath)) {
$paths[] = $themePath;
if (is_dir($themePath . '/views')) {
$paths[] = $themePath . '/views';
}
}
// 2. Priority: Parent Theme (if exists)
if (self::$parentTheme) {
$parentPath = $themesDir . '/' . self::$parentTheme;
if (is_dir($parentPath)) {
$paths[] = $parentPath;
if (is_dir($parentPath . '/views')) {
$paths[] = $parentPath . '/views';
}
}
}
// 3. Global templates
if (is_dir($templatesDir)) {
$paths[] = $templatesDir;
}
// 4. Modules
if (is_dir($modulesDir)) {
$paths[] = $modulesDir;
}
// Custom views path if provided
if ($viewsPath && is_dir($viewsPath)) {
array_unshift($paths, $viewsPath);
}
$loader = new FilesystemLoader($paths);
// Register namespaces for modules based on directory structure
if (is_dir($modulesDir)) {
$modules = array_diff(scandir($modulesDir), ['.', '..']);
foreach ($modules as $module) {
$moduleViewsPath = $modulesDir . '/' . $module . '/views';
if (is_dir($moduleViewsPath)) {
// Check for theme override for the entire module namespace
$themeModulePath = $themesDir . '/' . self::$activeTheme . '/modules/' . $module . '/views';
if (is_dir($themeModulePath)) {
$loader->addPath($themeModulePath, $module);
}
// Check for parent theme override
if (self::$parentTheme) {
$parentModulePath = $themesDir . '/' . self::$parentTheme . '/modules/' . $module . '/views';
if (is_dir($parentModulePath)) {
$loader->addPath($parentModulePath, $module);
}
}
// Add original module path as fallback
$loader->addPath($moduleViewsPath, $module);
}
}
}
self::$loader = $loader;
$this->twig = new Environment($loader, [
'cache' => $baseDir . '/cache/twig',
'auto_reload' => true,
'autoescape' => 'html',
]);
self::$environment = $this->twig;
// Register default functions
$this->registerDefaultFunctions();
}
protected function registerDefaultFunctions(): void
{
// Load theme settings
$themeSettings = [];
if (self::$activeTheme) {
try {
$dbKey = 'theme_settings_' . self::$activeTheme;
$config = \Illuminate\Database\Capsule\Manager::table('system_config')
->where('system_config_key', $dbKey)
->first();
if ($config) {
$themeSettings = json_decode($config->system_config_value, true) ?: [];
}
// If it's a child theme, we might want to merge with parent settings or defaults
// For now, let's also load the customizer defaults if the key is missing
$baseDir = dirname(__DIR__);
$customizerPath = $baseDir . '/themes/' . self::$activeTheme . '/customizer.json';
if (file_exists($customizerPath)) {
$customizer = json_decode(file_get_contents($customizerPath), true);
if (isset($customizer['sections'])) {
foreach ($customizer['sections'] as $section) {
foreach ($section['fields'] as $field) {
if (!isset($themeSettings[$field['id']])) {
$themeSettings[$field['id']] = $field['default'];
}
}
}
}
}
} catch (\Exception $e) {
// Ignore DB errors here
}
}
$this->twig->addGlobal('theme_settings', $themeSettings);
// Platform name global variable
try {
$platformService = new \App\Services\PlatformService();
$this->twig->addGlobal('platformName', $platformService->getPlatformName());
} catch (\Exception $e) {
$this->twig->addGlobal('platformName', 'SysCMS');
}
// Translation function __() available in Twig views
$this->twig->addFunction(new TwigFunction('__', function ($key, ...$params) {
return __($key, ...$params);
}));
$this->twig->addFunction(new TwigFunction('url', function ($path = '') {
return url($path);
}));
$this->twig->addFunction(new TwigFunction('asset_url', function ($path) {
return asset_url($path);
}));
$this->twig->addFunction(new TwigFunction('theme_url', function ($path) {
$basePath = '/themes/' . (self::$activeTheme ?? 'default');
$fullPath = dirname(__DIR__) . $basePath . '/' . ltrim($path, '/');
// If asset doesn't exist in child theme, use parent theme
if (!file_exists($fullPath) && self::$parentTheme) {
return '/themes/' . self::$parentTheme . '/' . ltrim($path, '/');
}
return $basePath . '/' . ltrim($path, '/');
}));
$this->twig->addFunction(new TwigFunction('theme_asset', function ($path) {
$basePath = '/themes/' . (self::$activeTheme ?? 'default') . '/assets';
$fullPath = dirname(__DIR__) . $basePath . '/' . ltrim($path, '/');
// If asset doesn't exist in child theme, use parent theme
if (!file_exists($fullPath) && self::$parentTheme) {
return '/themes/' . self::$parentTheme . '/assets/' . ltrim($path, '/');
}
return $basePath . '/' . ltrim($path, '/');
}));
$this->twig->addFunction(new TwigFunction('bus_module_enabled', function ($moduleName) {
return bus_module_enabled($moduleName);
}));
$this->twig->addFunction(new TwigFunction('get_config', function ($key, $default = null) {
return get_config($key, $default);
}));
$this->twig->addGlobal('active_theme', self::$activeTheme);
$this->twig->addGlobal('parent_theme', self::$parentTheme);
$this->twig->addGlobal('current_uri', $_SERVER['REQUEST_URI'] ?? '/');
}
/**
* Renders a view with theme override support
*/
public function render(string $template, array $data = []): string
{
// Core Integrity Check (License Redundancy)
if (!\Kernel\License::isValidDomain()) {
throw new \RuntimeException('Core Integrity Error: Render Engine Initialization Failed (0x8F2).');
}
// Check for theme overrides (Child Theme first, then Parent Theme)
$overrideTemplate = $this->getThemeOverride($template);
$templateToRender = $overrideTemplate ?: $template;
return $this->twig->render($templateToRender, $data);
}
/**
* Checks if a theme override exists for a given template
*/
protected function getThemeOverride(string $template): ?string
{
if (self::$activeTheme === null) {
return null;
}
$baseDir = dirname(__DIR__);
$themes = [self::$activeTheme];
if (self::$parentTheme) {
$themes[] = self::$parentTheme;
}
// Handle namespaced templates like @index/index.twig
if (strpos($template, '@') === 0) {
$parts = explode('/', ltrim($template, '@'), 2);
if (count($parts) === 2) {
$module = $parts[0];
$view = $parts[1];
foreach ($themes as $theme) {
$themePath = $baseDir . '/themes/' . $theme;
// Check in themes/{theme}/modules/{module}/views/{view}
$checkPath = $themePath . '/modules/' . $module . '/views/' . $view;
if (file_exists($checkPath)) {
return 'modules/' . $module . '/views/' . $view;
}
// Also check without 'views' subfolder
$checkPathAlt = $themePath . '/modules/' . $module . '/' . $view;
if (file_exists($checkPathAlt)) {
return 'modules/' . $module . '/' . $view;
}
}
}
}
return null;
}
/**
* Directly displays the view
*/
public function display(string $template, array $data = []): void
{
echo $this->render($template, $data);
}
/**
* Returns the Twig instance
*/
public function getEngine(): Environment
{
return $this->twig;
}
/**
* Set the Twig loader
*/
public static function setLoader(\Twig\Loader\LoaderInterface $loader): void
{
self::$loader = $loader;
}
public static function getLoader(): ?\Twig\Loader\LoaderInterface
{
return self::$loader;
}
public static function getActiveTheme(): ?string
{
return self::$activeTheme;
}
public static function getParentTheme(): ?string
{
return self::$parentTheme;
}
/**
* Set the Twig environment
*/
public static function setEnvironment(\Twig\Environment $environment): void
{
self::$environment = $environment;
}
/**
* Add a path for module views
*/
public static function addPath(string $namespace, string $path): void
{
if (self::$loader instanceof \Twig\Loader\FilesystemLoader) {
self::$loader->addPath($path, $namespace);
}
}
}