► Services Loaded
diff --git a/app/routes.php b/app/routes.php
index bfe674b..579f575 100644
--- a/app/routes.php
+++ b/app/routes.php
@@ -2,14 +2,31 @@
use ZEngine\Core\Http\Request;
use ZEngine\Core\Http\Response;
+use ZEngine\App\Controllers\WelcomeController;
+use ZEngine\App\Middleware\GuestMiddleware;
$router = router();
-$router->get('/', function (Request $request) {
- $version = app()->version();
- return view('welcome', [
- 'version' => $version,
- 'services_count' => app()->getContainer()->count()
+if (env('MAINTENANCE_MODE', 0)) {
+ $clientIp = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
+ $whitelistedIps = env('WHITELISTED_IPS', []);
+
+ if (!in_array($clientIp, $whitelistedIps)) {
+ http_response_code(503);
+ include __DIR__ . '/Views/errors/maintenance.php';
+ exit;
+ }
+}
+
+$router->group(['middleware' => [GuestMiddleware::class]], function ($router) {
+ $router->get('/', [WelcomeController::class, 'showWelcome']);
+ $router->get('/welcome', [WelcomeController::class, 'showWelcome']);
+});
+
+$router->get('/say/{msg}', function ($msg) {
+ return json([
+ 'success' => true,
+ 'message' => $msg
]);
});
diff --git a/composer.json b/composer.json
index 484c880..4c8e9b1 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,6 @@
{
"name": "spexdw/z-engine",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "ZEngine - A modern, lightweight PHP framework with powerful service architecture",
"type": "project",
"keywords": ["framework", "php", "zengine", "service-oriented", "routing"],
@@ -12,7 +12,8 @@
}
],
"require": {
- "php": "^8.1"
+ "php": "^8.1",
+ "phpmailer/phpmailer": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
diff --git a/config/.htaccess b/config/.htaccess
new file mode 100644
index 0000000..14249c5
--- /dev/null
+++ b/config/.htaccess
@@ -0,0 +1 @@
+Deny from all
\ No newline at end of file
diff --git a/config/env.php b/config/env.php
new file mode 100644
index 0000000..ac7d12f
--- /dev/null
+++ b/config/env.php
@@ -0,0 +1,50 @@
+ 'ZEngine',
+ 'APP_ENV' => 'local',
+ 'APP_KEY' => 'app_key',
+ 'APP_DEBUG' => true,
+ 'APP_URL' => 'http://localhost:1999',
+ 'APP_TIMEZONE' => 'UTC',
+ 'APP_LOCALE' => 'en',
+ 'CRON_KEY' => 'zengine_cron_key',
+
+ 'DB_CONNECTION' => 'mysql',
+ 'DB_HOST' => 'localhost',
+ 'DB_PORT' => 3306,
+ 'DB_DATABASE' => 'zengine',
+ 'DB_USERNAME' => 'root',
+ 'DB_PASSWORD' => '',
+
+ 'SESSION_NAME' => 'ZENGINE_SESSION',
+ 'SESSION_LIFETIME' => 7200,
+ 'SESSION_PATH' => '/',
+ 'SESSION_DOMAIN' => '',
+ 'SESSION_SECURE' => true,
+ 'SESSION_HTTPONLY' => true,
+ 'SESSION_SAMESITE' => 'Lax',
+
+ 'COOKIE_EXPIRE' => 3600,
+ 'COOKIE_PATH' => '/',
+ 'COOKIE_DOMAIN' => '',
+ 'COOKIE_SECURE' => true,
+ 'COOKIE_HTTPONLY' => true,
+ 'COOKIE_SAMESITE' => 'Lax',
+
+ 'SMTP_HOST' => '',
+ 'SMTP_PORT' => 587,
+ 'SMTP_USERNAME' => '',
+ 'SMTP_PASSWORD' => '',
+ 'SMTP_ENCRYPTION' => 'tls',
+ 'SMTP_FROM_ADDRESS' => '',
+ 'SMTP_FROM_NAME' => '',
+ 'SMTP_TIMEOUT' => 30,
+ 'SMTP_AUTH' => true,
+ 'SMTP_DEBUG' => 0,
+
+
+ 'MAINTENANCE_MODE' => 0,
+ 'MAINTENANCE_MESSAGE' => 'We are currently performing maintenance. Please check back soon.',
+ 'WHITELISTED_IPS' => ['127.0.0.1', '::1'],
+];
diff --git a/config/mail.php b/config/mail.php
new file mode 100644
index 0000000..eb13549
--- /dev/null
+++ b/config/mail.php
@@ -0,0 +1,16 @@
+ env('APP_NAME'),
+ 'APP_URL' => env('APP_URL'),
+ 'SMTP_HOST' => env('SMTP_HOST'),
+ 'SMTP_PORT' => env('SMTP_PORT'),
+ 'SMTP_USERNAME' => env('SMTP_USERNAME'),
+ 'SMTP_PASSWORD' => env('SMTP_PASSWORD'),
+ 'SMTP_ENCRYPTION' => env('SMTP_ENCRYPTION'),
+ 'SMTP_FROM_ADDRESS' => env('SMTP_FROM_ADDRESS'),
+ 'SMTP_FROM_NAME' => env('SMTP_FROM_NAME'),
+ 'SMTP_TIMEOUT' => env('SMTP_TIMEOUT'),
+ 'SMTP_AUTH' => env('SMTP_AUTH'),
+ 'SMTP_DEBUG' => env('SMTP_DEBUG')
+];
diff --git a/core/.htaccess b/core/.htaccess
new file mode 100644
index 0000000..14249c5
--- /dev/null
+++ b/core/.htaccess
@@ -0,0 +1 @@
+Deny from all
\ No newline at end of file
diff --git a/core/Http/Request.php b/core/Http/Request.php
index 9368982..dce2c83 100644
--- a/core/Http/Request.php
+++ b/core/Http/Request.php
@@ -10,6 +10,7 @@ class Request
private array $files;
private array $cookies;
private array $headers;
+ private array $params = [];
private ?string $content = null;
public function __construct(
@@ -35,15 +36,26 @@ public static function capture(): self
private function extractHeaders(): array
{
$headers = [];
+
foreach ($this->server as $key => $value) {
if (str_starts_with($key, 'HTTP_')) {
$headerName = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
$headers[$headerName] = $value;
}
}
+
+ if (isset($this->server['CONTENT_TYPE'])) {
+ $headers['Content-Type'] = $this->server['CONTENT_TYPE'];
+ }
+
+ if (isset($this->server['CONTENT_LENGTH'])) {
+ $headers['Content-Length'] = $this->server['CONTENT_LENGTH'];
+ }
+
return $headers;
}
+
public function method(): string
{
return strtoupper($this->server['REQUEST_METHOD'] ?? 'GET');
@@ -71,16 +83,26 @@ public function post(string $key, mixed $default = null): mixed
public function input(string $key, mixed $default = null): mixed
{
+ if ($this->isJson()) {
+ return $this->json($key, $default);
+ }
return $this->request[$key] ?? $this->query[$key] ?? $default;
}
public function all(): array
{
+ if ($this->isJson()) {
+ return $this->json() ?? [];
+ }
return array_merge($this->query, $this->request);
}
public function has(string $key): bool
{
+ if ($this->isJson()) {
+ $data = $this->json();
+ return isset($data[$key]);
+ }
return isset($this->request[$key]) || isset($this->query[$key]);
}
@@ -152,4 +174,26 @@ public function isAjax(): bool
{
return $this->header('X-Requested-With') === 'XMLHttpRequest';
}
+
+ public function setParams(array $params): void
+ {
+ $this->params = $params;
+ }
+
+ public function param(string $key, mixed $default = null): mixed
+ {
+ return $this->params[$key] ?? $default;
+ }
+
+ private array $apiData = [];
+
+ public function setApiKey(array $key): void
+ {
+ $this->apiData['key'] = $key;
+ }
+
+ public function getApiKey(): ?array
+ {
+ return $this->apiData['key'] ?? null;
+ }
}
diff --git a/core/Providers.php b/core/Providers.php
index 5082a84..27efdfb 100644
--- a/core/Providers.php
+++ b/core/Providers.php
@@ -10,6 +10,7 @@
use ZEngine\Core\Services\ValidationService;
use ZEngine\Core\Services\LoggerService;
use ZEngine\Core\Services\HashService;
+use ZEngine\Core\Services\MailService;
class Providers
{
@@ -23,6 +24,7 @@ public static function register(Container $container): void
self::registerValidation($container);
self::registerLogger($container);
self::registerHash($container);
+ self::registerMail($container);
}
private static function registerDatabase(Container $container): void
@@ -37,7 +39,7 @@ private static function registerDatabase(Container $container): void
private static function registerSession(Container $container): void
{
$container->singleton('session', function () {
- return new SessionService();
+ return SessionService::getInstance();
});
$container->alias('session', SessionService::class);
}
@@ -90,4 +92,13 @@ private static function registerHash(Container $container): void
});
$container->alias('hash', HashService::class);
}
+
+ private static function registerMail(Container $container): void
+ {
+ $container->singleton('mail', function () {
+ return new MailService(config('mail'));
+ });
+ $container->alias('mail', MailService::class);
+ }
+
}
diff --git a/core/Router/Router.php b/core/Router/Router.php
index fa51cd9..807230e 100644
--- a/core/Router/Router.php
+++ b/core/Router/Router.php
@@ -12,6 +12,7 @@ class Router
private array $routes = [];
private array $middleware = [];
private Container $container;
+ private array $currentMiddleware = [];
public function __construct(Container $container)
{
@@ -55,7 +56,8 @@ public function any(string $uri, Closure|array|string $action): Route
private function addRoute(string $method, string $uri, Closure|array|string $action): Route
{
- $route = new Route($method, $uri, $action);
+ $fullUri = $this->currentPrefix . $uri;
+ $route = new Route($method, $fullUri, $action);
$this->routes[$method][] = $route;
return $route;
}
@@ -66,21 +68,33 @@ public function group(array $attributes, Closure $callback): void
$middleware = $attributes['middleware'] ?? [];
$previousPrefix = $this->getCurrentPrefix();
- $this->setPrefix($prefix);
+ $previousMiddleware = $this->currentMiddleware ?? [];
- $callback($this);
+ $this->setPrefix($previousPrefix . $prefix);
+ $this->currentMiddleware = array_merge($previousMiddleware, (array) $middleware);
- $this->setPrefix($previousPrefix);
+ $beforeCounts = [];
+ foreach ($this->routes as $method => $routes) {
+ $beforeCounts[$method] = count($routes);
+ }
+
+ $callback($this);
- if (!empty($middleware)) {
- foreach ($this->getLastGroupRoutes() as $route) {
- $route->middleware($middleware);
+ foreach ($this->routes as $method => $routes) {
+ $startIndex = $beforeCounts[$method] ?? 0;
+ for ($i = $startIndex; $i < count($routes); $i++) {
+ if (!empty($this->currentMiddleware)) {
+ $this->routes[$method][$i]->middleware($this->currentMiddleware);
+ }
}
}
+
+ $this->setPrefix($previousPrefix);
+ $this->currentMiddleware = $previousMiddleware;
}
private string $currentPrefix = '';
- private int $lastRouteCount = 0;
+ private array $lastRouteCounts = [];
private function getCurrentPrefix(): string
{
@@ -89,7 +103,10 @@ private function getCurrentPrefix(): string
private function setPrefix(string $prefix): void
{
- $this->lastRouteCount = count($this->routes);
+ $this->lastRouteCounts = [];
+ foreach ($this->routes as $method => $methodRoutes) {
+ $this->lastRouteCounts[$method] = count($methodRoutes);
+ }
$this->currentPrefix = $prefix;
}
@@ -97,7 +114,9 @@ private function getLastGroupRoutes(): array
{
$routes = [];
foreach ($this->routes as $method => $methodRoutes) {
- $routes = array_merge($routes, array_slice($methodRoutes, $this->lastRouteCount));
+ $startIndex = $this->lastRouteCounts[$method] ?? 0;
+ $newRoutes = array_slice($methodRoutes, $startIndex);
+ $routes = array_merge($routes, $newRoutes);
}
return $routes;
}
@@ -123,6 +142,8 @@ public function dispatch(Request $request): Response
private function runRoute(Route $route, array $params, Request $request): Response
{
+ $request->setParams($params);
+
$middlewares = $route->getMiddleware();
$pipeline = array_reduce(
diff --git a/core/services/Cache-Service.php b/core/Services/CacheService.php
similarity index 70%
rename from core/services/Cache-Service.php
rename to core/Services/CacheService.php
index b61dcf0..22b07c7 100644
--- a/core/services/Cache-Service.php
+++ b/core/Services/CacheService.php
@@ -19,6 +19,8 @@ public function __construct(array $config = [])
public function get(string $key, mixed $default = null): mixed
{
+ $this->autoCleanup();
+
$file = $this->getFilePath($key);
if (!file_exists($file)) {
@@ -114,4 +116,47 @@ private function getFilePath(string $key): string
{
return $this->cacheDir . '/' . md5($key) . '.cache';
}
+
+ public function cleanup(): int
+ {
+ $maxAge = 3 * 3600; // 3 hours
+ $cutoffTime = time() - $maxAge;
+ $deleted = 0;
+
+ $files = glob($this->cacheDir . '/*.cache');
+
+ foreach ($files as $file) {
+ if (!is_file($file)) {
+ continue;
+ }
+
+ if (filemtime($file) < $cutoffTime) {
+ if (unlink($file)) {
+ $deleted++;
+ }
+ continue;
+ }
+
+ try {
+ $data = @unserialize(file_get_contents($file));
+ if ($data && isset($data['expires_at']) && $data['expires_at'] < time()) {
+ if (unlink($file)) {
+ $deleted++;
+ }
+ }
+ } catch (\Exception $e) {
+ @unlink($file);
+ $deleted++;
+ }
+ }
+
+ return $deleted;
+ }
+
+ private function autoCleanup(): void
+ {
+ if (rand(1, 100) <= 2) {
+ $this->cleanup();
+ }
+ }
}
diff --git a/core/services/Cookie-Service.php b/core/Services/CookieService.php
similarity index 100%
rename from core/services/Cookie-Service.php
rename to core/Services/CookieService.php
diff --git a/core/services/Database-Service.php b/core/Services/DatabaseService.php
similarity index 85%
rename from core/services/Database-Service.php
rename to core/Services/DatabaseService.php
index a0629e3..af1bcb1 100644
--- a/core/services/Database-Service.php
+++ b/core/Services/DatabaseService.php
@@ -236,4 +236,43 @@ public function count(): int
$result = $this->db->query($sql, $this->bindings);
return (int) ($result[0]['count'] ?? 0);
}
+
+ public function update(array $data): int
+ {
+ $set = implode(', ', array_map(fn($key) => "{$key} = ?", array_keys($data)));
+
+ $sql = "UPDATE {$this->table} SET {$set}";
+
+ $bindings = array_values($data);
+
+ if (!empty($this->wheres)) {
+ $sql .= " WHERE " . implode(' AND ', $this->wheres);
+ $bindings = array_merge($bindings, $this->bindings);
+ }
+
+ return $this->db->execute($sql, $bindings);
+ }
+
+ public function delete(): int
+ {
+ $sql = "DELETE FROM {$this->table}";
+
+ if (!empty($this->wheres)) {
+ $sql .= " WHERE " . implode(' AND ', $this->wheres);
+ }
+
+ return $this->db->execute($sql, $this->bindings);
+ }
+
+ public function insert(array $data): int
+ {
+ $columns = implode(', ', array_keys($data));
+ $placeholders = implode(', ', array_fill(0, count($data), '?'));
+ $sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
+
+ $this->db->execute($sql, array_values($data));
+
+ return (int) $this->db->connect()->lastInsertId();
+ }
+
}
diff --git a/core/services/FormBuilder-Service.php b/core/Services/FormBuilderService.php
similarity index 100%
rename from core/services/FormBuilder-Service.php
rename to core/Services/FormBuilderService.php
diff --git a/core/services/Hash-Service.php b/core/Services/HashService.php
similarity index 100%
rename from core/services/Hash-Service.php
rename to core/Services/HashService.php
diff --git a/core/services/Logger-Service.php b/core/Services/LoggerService.php
similarity index 100%
rename from core/services/Logger-Service.php
rename to core/Services/LoggerService.php
diff --git a/core/Services/MailService.php b/core/Services/MailService.php
new file mode 100644
index 0000000..f349282
--- /dev/null
+++ b/core/Services/MailService.php
@@ -0,0 +1,58 @@
+config = $config;
+ $this->mailer = new PHPMailer(true);
+ $this->setupMailer();
+ }
+
+ private function setupMailer(): void
+ {
+ $this->mailer->isSMTP();
+ $this->mailer->Host = $this->config['SMTP_HOST'] ?? 'localhost';
+ $this->mailer->SMTPAuth = $this->config['SMTP_AUTH'] ?? true;
+ $this->mailer->Username = $this->config['SMTP_USERNAME'] ?? '';
+ $this->mailer->Password = $this->config['SMTP_PASSWORD'] ?? '';
+ $this->mailer->SMTPSecure = $this->config['SMTP_ENCRYPTION'] ?? 'tls';
+ $this->mailer->Port = $this->config['SMTP_PORT'] ?? 587;
+ $this->mailer->Timeout = $this->config['SMTP_TIMEOUT'] ?? 30;
+ $this->mailer->SMTPDebug = $this->config['SMTP_DEBUG'] ?? 0;
+ $this->mailer->CharSet = 'UTF-8';
+
+ $this->mailer->setFrom(
+ $this->config['SMTP_FROM_ADDRESS'] ?? 'noreply@zengine.app',
+ $this->config['SMTP_FROM_NAME'] ?? 'ZEngine'
+ );
+ }
+
+ public function send(string $to, string $subject, string $body, array $data = []): bool
+ {
+ try {
+ $this->mailer->clearAddresses();
+ $this->mailer->clearAttachments();
+
+ $this->mailer->addAddress($to);
+ $this->mailer->Subject = $subject;
+ $this->mailer->isHTML(true);
+ $this->mailer->Body = $body;
+ $this->mailer->AltBody = strip_tags($body);
+
+ return $this->mailer->send();
+ } catch (Exception $e) {
+ error_log('Mail error: ' . $e->getMessage());
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/core/Services/SessionService.php b/core/Services/SessionService.php
new file mode 100644
index 0000000..54887ec
--- /dev/null
+++ b/core/Services/SessionService.php
@@ -0,0 +1,287 @@
+sessionModel = new Session();
+ $this->fingerprint = $this->generateFingerprint();
+ $this->load();
+ $this->cleanupExpiredSessions();
+ }
+
+ public static function getInstance(): SessionService
+ {
+ if (self::$instance === null) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ private function load(): void
+ {
+ if ($this->loaded) {
+ return;
+ }
+
+ $this->sessionId = $_COOKIE[$this->cookieName] ?? null;
+
+ if ($this->sessionId && $this->validate()) {
+ $session = $this->sessionModel->find($this->sessionId);
+
+ if ($session) {
+ $this->data = $session['payload'] ?? [];
+ $this->sessionModel->updateActivity($this->sessionId);
+ $this->loaded = true;
+ return;
+ }
+ }
+
+ $this->createNewSession();
+ }
+
+ private function createNewSession(): void
+ {
+ $this->sessionId = $this->generateSessionId();
+ $this->data = [];
+
+ $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+ $this->sessionModel->create(
+ $this->sessionId,
+ null,
+ $ip,
+ $userAgent,
+ $this->data
+ );
+
+ $this->setCookie();
+ $this->loaded = true;
+ }
+
+ private function validate(): bool
+ {
+ if (!$this->sessionId) {
+ return false;
+ }
+
+ $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+ if (!$this->sessionModel->validateFingerprint($this->sessionId, $ip, $userAgent)) {
+ $this->invalidateSession();
+ return false;
+ }
+
+ return true;
+ }
+
+ private function invalidateSession(): void
+ {
+ if ($this->sessionId) {
+ $this->sessionModel->delete($this->sessionId);
+ }
+
+ $this->data = [];
+ $this->sessionId = null;
+ $this->loaded = false;
+ $this->deleteCookie();
+ }
+
+ public function set(string $key, $value): void
+ {
+ if (!$this->loaded) {
+ return;
+ }
+
+ $this->data[$key] = $value;
+ $this->save();
+ }
+
+ public function get(string $key, $default = null)
+ {
+ if (!$this->loaded) {
+ return $default;
+ }
+
+ return $this->data[$key] ?? $default;
+ }
+
+ public function has(string $key): bool
+ {
+ if (!$this->loaded) {
+ return false;
+ }
+
+ return isset($this->data[$key]);
+ }
+
+ public function remove(string $key): void
+ {
+ if (!$this->loaded) {
+ return;
+ }
+
+ unset($this->data[$key]);
+ $this->save();
+ }
+
+ public function regenerate(?int $userId = null): void
+ {
+ $oldSessionId = $this->sessionId;
+ $oldData = $this->data;
+
+ if ($oldSessionId) {
+ $this->sessionModel->delete($oldSessionId);
+ }
+
+ $this->sessionId = $this->generateSessionId();
+ $this->data = $oldData;
+
+ $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+ $this->sessionModel->create(
+ $this->sessionId,
+ $userId,
+ $ip,
+ $userAgent,
+ $this->data
+ );
+
+ $this->setCookie();
+ $this->loaded = true;
+ }
+
+ public function destroy(): void
+ {
+ if ($this->sessionId) {
+ $this->sessionModel->delete($this->sessionId);
+ }
+
+ $this->data = [];
+ $this->sessionId = null;
+ $this->loaded = false;
+
+ $this->deleteCookie();
+ }
+
+ public function associateUser(int $userId): void
+ {
+ $this->regenerate($userId);
+ $this->set('user_id', $userId);
+ }
+
+ public function token(): string
+ {
+ if (!$this->has('_token')) {
+ $this->set('_token', bin2hex(random_bytes(32)));
+ }
+
+ return $this->get('_token', '');
+ }
+
+ public function flash(string $key, $value): void
+ {
+ $this->set('_flash_' . $key, $value);
+ }
+
+ public function getFlash(string $key, $default = null)
+ {
+ $value = $this->get('_flash_' . $key, $default);
+ $this->remove('_flash_' . $key);
+ return $value;
+ }
+
+ public function hasFlash(string $key): bool
+ {
+ return $this->has('_flash_' . $key);
+ }
+
+ private function save(): void
+ {
+ if ($this->sessionId) {
+ $userId = isset($this->data['user_id']) ? (int)$this->data['user_id'] : null;
+ $this->sessionModel->update($this->sessionId, $this->data, null, $userId);
+ }
+ }
+
+ private function generateSessionId(): string
+ {
+ return bin2hex(random_bytes(32));
+ }
+
+ private function generateFingerprint(): string
+ {
+ $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+ return hash('sha256', $ip . '|' . $userAgent . '|' . env('APP_KEY', 'default-key'));
+ }
+
+ private function getFingerprintComponents(): array
+ {
+ $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
+
+ return [
+ hash('sha256', $ip),
+ hash('sha256', $userAgent)
+ ];
+ }
+
+ private function setCookie(): void
+ {
+ $secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on';
+
+ setcookie(
+ $this->cookieName,
+ $this->sessionId,
+ [
+ 'expires' => time() + $this->lifetime,
+ 'path' => '/',
+ 'domain' => '',
+ 'secure' => $secure,
+ 'httponly' => true,
+ 'samesite' => 'Strict'
+ ]
+ );
+ }
+
+ private function deleteCookie(): void
+ {
+ setcookie(
+ $this->cookieName,
+ '',
+ [
+ 'expires' => time() - 3600,
+ 'path' => '/',
+ 'domain' => '',
+ 'secure' => true,
+ 'httponly' => true,
+ 'samesite' => 'Strict'
+ ]
+ );
+ }
+
+ private function cleanupExpiredSessions(): void
+ {
+ if (rand(1, 100) <= 2) {
+ $this->sessionModel->cleanup($this->lifetime);
+ }
+ }
+}
diff --git a/core/services/Validation-Service.php b/core/Services/ValidationService.php
similarity index 100%
rename from core/services/Validation-Service.php
rename to core/Services/ValidationService.php
diff --git a/core/helpers.php b/core/helpers.php
index d556e93..92312f3 100644
--- a/core/helpers.php
+++ b/core/helpers.php
@@ -3,36 +3,14 @@
if (!function_exists('env')) {
function env(string $key, mixed $default = null): mixed
{
- static $loaded = false;
-
- if (!$loaded) {
- $envFile = dirname(__DIR__) . '/.env';
- if (file_exists($envFile)) {
- $lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
- foreach ($lines as $line) {
- if (strpos(trim($line), '#') === 0) {
- continue;
- }
- [$name, $value] = explode('=', $line, 2);
- $value = trim($value);
-
- if ($value === 'true' || $value === '(true)') {
- $value = true;
- } elseif ($value === 'false' || $value === '(false)') {
- $value = false;
- } elseif ($value === 'null' || $value === '(null)') {
- $value = null;
- } elseif ($value === 'empty' || $value === '(empty)') {
- $value = '';
- }
-
- $_ENV[trim($name)] = $value;
- }
- }
- $loaded = true;
+ static $config = null;
+
+ if ($config === null) {
+ $envFile = dirname(__DIR__) . '/config/env.php';
+ $config = file_exists($envFile) ? require $envFile : [];
}
- return $_ENV[$key] ?? $default;
+ return $config[$key] ?? $default;
}
}
@@ -196,3 +174,10 @@ function hash_service(): \ZEngine\Core\Services\HashService
return app('hash');
}
}
+
+if (!function_exists('mail')) {
+ function mail(): \ZEngine\Core\Services\MailService
+ {
+ return app('mail');
+ }
+}
diff --git a/core/services/Session-Service.php b/core/services/Session-Service.php
deleted file mode 100644
index c12479d..0000000
--- a/core/services/Session-Service.php
+++ /dev/null
@@ -1,182 +0,0 @@
-config = array_merge([
- 'name' => 'ZENGINE_SESSION',
- 'lifetime' => 7200,
- 'path' => '/',
- 'domain' => '',
- 'secure' => false,
- 'httponly' => true,
- 'samesite' => 'Lax',
- ], $config);
- }
-
- public function start(): bool
- {
- if ($this->started) {
- return true;
- }
-
- if (session_status() === PHP_SESSION_ACTIVE) {
- $this->started = true;
- return true;
- }
-
- session_name($this->config['name']);
-
- session_set_cookie_params([
- 'lifetime' => $this->config['lifetime'],
- 'path' => $this->config['path'],
- 'domain' => $this->config['domain'],
- 'secure' => $this->config['secure'],
- 'httponly' => $this->config['httponly'],
- 'samesite' => $this->config['samesite'],
- ]);
-
- $this->started = session_start();
-
- return $this->started;
- }
-
- public function set(string $key, mixed $value): void
- {
- $this->start();
- $_SESSION[$key] = $value;
- }
-
- public function get(string $key, mixed $default = null): mixed
- {
- $this->start();
- return $_SESSION[$key] ?? $default;
- }
-
- public function has(string $key): bool
- {
- $this->start();
- return isset($_SESSION[$key]);
- }
-
- public function delete(string $key): void
- {
- $this->start();
- unset($_SESSION[$key]);
- }
-
- public function all(): array
- {
- $this->start();
- return $_SESSION;
- }
-
- public function clear(): void
- {
- $this->start();
- $_SESSION = [];
- }
-
- public function destroy(): bool
- {
- $this->start();
- $_SESSION = [];
-
- if (isset($_COOKIE[session_name()])) {
- $params = session_get_cookie_params();
- setcookie(
- session_name(),
- '',
- time() - 42000,
- $params['path'],
- $params['domain'],
- $params['secure'],
- $params['httponly']
- );
- }
-
- return session_destroy();
- }
-
- public function regenerate(bool $deleteOld = true): bool
- {
- $this->start();
- return session_regenerate_id($deleteOld);
- }
-
- public function flash(string $key, mixed $value): void
- {
- $this->set($key, $value);
- $this->set('_flash_keys', array_merge($this->get('_flash_keys', []), [$key]));
- }
-
- public function getFlash(string $key, mixed $default = null): mixed
- {
- $value = $this->get($key, $default);
- $this->delete($key);
-
- $flashKeys = $this->get('_flash_keys', []);
- if (($index = array_search($key, $flashKeys)) !== false) {
- unset($flashKeys[$index]);
- $this->set('_flash_keys', $flashKeys);
- }
-
- return $value;
- }
-
- public function keep(array $keys): void
- {
- $flashKeys = $this->get('_flash_keys', []);
- $this->set('_flash_keys', array_diff($flashKeys, $keys));
- }
-
- public function reflash(): void
- {
- $flashKeys = $this->get('_flash_keys', []);
- $this->keep($flashKeys);
- }
-
- public function getId(): string
- {
- $this->start();
- return session_id();
- }
-
- public function setId(string $id): void
- {
- session_id($id);
- }
-
- public function pull(string $key, mixed $default = null): mixed
- {
- $value = $this->get($key, $default);
- $this->delete($key);
- return $value;
- }
-
- public function push(string $key, mixed $value): void
- {
- $array = $this->get($key, []);
- $array[] = $value;
- $this->set($key, $array);
- }
-
- public function increment(string $key, int $amount = 1): int
- {
- $value = (int) $this->get($key, 0);
- $value += $amount;
- $this->set($key, $value);
- return $value;
- }
-
- public function decrement(string $key, int $amount = 1): int
- {
- return $this->increment($key, -$amount);
- }
-}
diff --git a/cron.php b/cron.php
new file mode 100644
index 0000000..52cf18b
--- /dev/null
+++ b/cron.php
@@ -0,0 +1,34 @@
+handle();
+
+} catch (\Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+}
+
+echo "\n=== Cron Jobs Completed ===\n";
diff --git a/error-handler.php b/error-handler.php
new file mode 100644
index 0000000..8ae22c7
--- /dev/null
+++ b/error-handler.php
@@ -0,0 +1,13 @@
+