415 lines
14 KiB
PHP
Executable File
415 lines
14 KiB
PHP
Executable File
<?php
|
|
|
|
namespace Kernel;
|
|
|
|
use DI\Container;
|
|
use DI\ContainerBuilder;
|
|
use FastRoute\RouteCollector;
|
|
use Illuminate\Database\Capsule\Manager as Capsule;
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use function FastRoute\simpleDispatcher;
|
|
|
|
class Application
|
|
{
|
|
protected static ?Application $instance = null;
|
|
protected Container $container;
|
|
protected Request $request;
|
|
protected array $routes = [];
|
|
protected ModuleLoader $moduleLoader;
|
|
protected $dispatcher;
|
|
protected HookManager $hooks;
|
|
protected array $publicPaths = [];
|
|
protected array $globalMiddlewares = [];
|
|
protected array $protectedMiddlewares = [];
|
|
|
|
public function __construct()
|
|
{
|
|
self::$instance = $this;
|
|
load_env(); // Uses the centralized helper
|
|
|
|
$this->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 '<input type="hidden" name="_token" value="' . ($_SESSION['_token'] ?? '') . '">';
|
|
}, ['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("<h1>500 - Internal Server Error</h1><p>An unexpected error occurred.</p>", 500);
|
|
}
|
|
}
|
|
|
|
$response->send();
|
|
}
|
|
}
|