initSession(); $this->initContainer(); $this->initDatabase(); $this->hooks = new HookManager(); $this->initModuleLoader(); $this->initTwig(); $this->initRouter(); } /** * Gets the application instance */ public static function getInstance(): ?Application { return self::$instance; } /** * Returns the DI Container */ public function getContainer(): Container { return $this->container; } public function getHookManager(): HookManager { return $this->hooks; } /** * Initializes the session */ protected function initSession(): void { // Session settings based on .env $lifetime = $_ENV['SESSION_LIFETIME'] ?? 120; $path = '/'; $domain = ''; $secure = false; $httponly = true; ini_set('session.gc_maxlifetime', $lifetime * 60); ini_set('session.gc_probability', 1); ini_set('session.gc_divisor', 100); ini_set('session.use_strict_mode', '1'); // Use memory (RAM) for sessions via /dev/shm to avoid disk I/O and permission issues $sessionPath = '/dev/shm'; // Fallback to system temp if /dev/shm doesn't exist (e.g. non-Linux environment) if (!is_dir($sessionPath) || !is_writable($sessionPath)) { $sessionPath = sys_get_temp_dir(); } session_save_path($sessionPath); session_set_cookie_params($lifetime * 60, $path, $domain, $secure, $httponly); if (session_status() === PHP_SESSION_NONE) { session_start(); } // Generate CSRF token if it doesn't exist if (!isset($_SESSION['_token'])) { $_SESSION['_token'] = bin2hex(random_bytes(32)); } // Define default language if not set if (!isset($_SESSION['lang'])) { $_SESSION['lang'] = is_multi_lang_enabled() ? ($_ENV['APP_LANG_DEFAULT'] ?? 'pt_BR') : ($_ENV['APP_LANG_DEFAULT'] ?? 'pt_BR'); } // Update last activity $_SESSION['last_activity'] = time(); } /** * Creates the DI Container */ protected function initContainer(): void { $builder = new ContainerBuilder(); $builder->useAutowiring(true); // $builder->useAnnotations(false); // Method removed in recent PHP-DI versions // If you have definitions if (file_exists(__DIR__ . '/../config/container.php')) { $builder->addDefinitions(__DIR__ . '/../config/container.php'); } $this->container = $builder->build(); $this->container->set(Container::class, $this->container); } /** * Initializes the database with Illuminate Database */ protected function initDatabase(): void { $capsule = new Capsule; $capsule->addConnection(get_db_config()); $capsule->setAsGlobal(); $capsule->bootEloquent(); } /** * Initializes the template system (Twig) */ protected function initTwig(): void { // Use Kernel\View to initialize the engine with theme support $view = new View(); $twig = $view->getEngine(); // Add extra global functions if needed (most are now in Kernel\View) // Add is_multi_lang_enabled function to templates $twig->addFunction(new \Twig\TwigFunction('is_multi_lang_enabled', function () { return ($_ENV['APP_MULTI_LANG'] ?? 'false') === 'true'; })); // Add CSRF function $twig->addFunction(new \Twig\TwigFunction('csrf_field', function () { return ''; }, ['is_safe' => ['html']])); // Add old function to retrieve form values $twig->addFunction(new \Twig\TwigFunction('old', function ($key, $default = '') { return $_POST[$key] ?? $default; })); // Add session function to access session data $twig->addFunction(new \Twig\TwigFunction('session', function ($key = null) { if ($key === null) { return $_SESSION; } return $_SESSION[$key] ?? null; })); // Add company_date filter $twig->addFilter(new \Twig\TwigFilter('company_date', function ($date, $includeTime = false) { return \App\Helpers\DateFormatter::format($date, $includeTime); })); } /** * Loads modules via ModuleLoader */ protected function initModuleLoader(): void { $this->moduleLoader = new ModuleLoader(dirname(__DIR__) . '/modules'); $this->moduleLoader->registerAutoload(); $this->moduleLoader->loadAll(); // Initialize translator after modules are loaded $templatesPath = dirname(__DIR__) . '/templates/locales'; if (is_dir($templatesPath)) { \Kernel\Translator::addNamespace('global', $templatesPath); } // Add theme locales if present (Parent Theme first, then Active Theme for overrides) $parentTheme = \Kernel\View::getParentTheme(); if ($parentTheme) { $parentLocalesPath = dirname(__DIR__) . '/themes/' . $parentTheme . '/locales'; if (is_dir($parentLocalesPath)) { \Kernel\Translator::addThemePath($parentLocalesPath); } } $activeTheme = \Kernel\View::getActiveTheme(); if ($activeTheme) { $themeLocalesPath = dirname(__DIR__) . '/themes/' . $activeTheme . '/locales'; if (is_dir($themeLocalesPath)) { \Kernel\Translator::addThemePath($themeLocalesPath); } } $locale = $_SESSION['lang'] ?? ($_ENV['APP_LANG_DEFAULT'] ?? 'pt_BR'); \Kernel\Translator::setLocale($locale); } /** * Initializes the routing system */ protected function initRouter(): void { // Load web routes if (file_exists(__DIR__ . '/../routes/web.php')) { $routeLoader = require __DIR__ . '/../routes/web.php'; $routeLoader($this); } $this->dispatcher = simpleDispatcher(function (RouteCollector $r) { foreach ($this->routes as $route) { [$method, $uri, $handler] = $route; $r->addRoute($method, $uri, $handler); } }); // Debug // $debugData = date('Y-m-d H:i:s') . " - Init Router with " . count($this->routes) . " routes\n"; // file_put_contents(__DIR__ . '/../debug_log.txt', $debugData, FILE_APPEND); } /** * Allows modules to register routes */ public function addRoute(string $method, string $uri, $handler): void { $this->routes[] = [$method, $uri, $handler]; } /** * Returns all registered routes */ public function getRoutes(): array { return $this->routes; } /** * Adds a public path (which does not require authentication) */ public function addPublicPath(string $path): void { $this->publicPaths[] = $path; } /** * Checks if the path is public */ public function isPublicPath(string $path): bool { foreach ($this->publicPaths as $publicPath) { if (str_ends_with($publicPath, '*')) { $prefix = rtrim($publicPath, '*'); if (strpos($path, $prefix) === 0) { return true; } } else { if ($path === $publicPath) { return true; } } } return false; } /** * Adds a global middleware (executed on all requests) */ public function addGlobalMiddleware(object $middleware): void { $this->globalMiddlewares[] = $middleware; } /** * Adds a middleware that will be executed on protected routes */ public function addProtectedMiddleware(object $middleware): void { $this->protectedMiddlewares[] = $middleware; } /** * Runs the application */ public function run(): void { // Maintenance mode check if (file_exists(dirname(__DIR__) . '/storage/framework/down')) { $data = json_decode(file_get_contents(dirname(__DIR__) . '/storage/framework/down'), true); $view = new \Kernel\View(); $response = new Response($view->render('errors/503.twig', [ 'message' => $data['message'] ?? 'O sistema está em manutenção.', 'retry' => $data['retry'] ?? null ]), 503); $response->send(); return; } Request::enableHttpMethodParameterOverride(); $this->request = Request::createFromGlobals(); // Standard Public Paths (Core) $this->addPublicPath('/language/*'); $this->addPublicPath('/storage/*'); $this->addPublicPath('/captcha'); try { $routeInfo = $this->dispatcher->dispatch( $this->request->getMethod(), $this->request->getPathInfo() ); switch ($routeInfo[0]) { case \FastRoute\Dispatcher::NOT_FOUND: $view = new \Kernel\View(); $response = new Response($view->render('errors/404.twig'), 404); break; case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: $response = new Response("405 - Method Not Allowed", 405); break; case \FastRoute\Dispatcher::FOUND: $handler = $routeInfo[1]; $vars = $routeInfo[2]; // Apply Global Middlewares foreach ($this->globalMiddlewares as $middleware) { if (method_exists($middleware, 'handle')) { $result = $middleware->handle($this->request, function ($req) { return $req; }); if ($result instanceof Response) { $response = $result; break 2; // Break out of switch } } } // Check Public Access $path = $this->request->getPathInfo(); if (!$this->isPublicPath($path)) { // Apply protected middlewares (e.g., Auth, Company) foreach ($this->protectedMiddlewares as $middleware) { if (method_exists($middleware, 'handle')) { $result = $middleware->handle($this->request, function ($req) { return $req; }); if ($result instanceof Response) { $response = $result; break 2; // Break out of switch } } } } // Execute the controller method if (is_array($handler) && isset($handler[0], $handler[1])) { $controller = $this->container->get($handler[0]); $method = $handler[1]; $response = $controller->$method($this->request, ...array_values($vars)); } else { $response = $handler($this->request, ...array_values($vars)); } if (!$response instanceof Response) { $response = new Response($response); } break; } } catch (\Throwable $e) { // Log the error if ($this->container->has('logging')) { // $logger = $this->container->get('logging'); // $logger->error($e->getMessage(), ['trace' => $e->getTraceAsString()]); } error_log($e->getMessage() . "\n" . $e->getTraceAsString()); if (env('APP_DEBUG', false)) { throw $e; } try { $view = new \Kernel\View(); $response = new Response($view->render('errors/500.twig'), 500); } catch (\Throwable $viewError) { // Fallback if view rendering fails $response = new Response("
An unexpected error occurred.
", 500); } } $response->send(); } }