Initial Commit

This commit is contained in:
Hosteroid
2025-10-08 14:23:07 +03:00
commit b3b3ac66ff
78 changed files with 14248 additions and 0 deletions

32
core/Application.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace Core;
class Application
{
public static Router $router;
public static Database $db;
public function __construct()
{
self::$router = new Router();
self::$db = new Database();
}
public function run()
{
try {
self::$router->resolve();
} catch (\Exception $e) {
http_response_code(500);
if ($_ENV['APP_ENV'] === 'development') {
echo '<h1>Error</h1>';
echo '<pre>' . $e->getMessage() . '</pre>';
echo '<pre>' . $e->getTraceAsString() . '</pre>';
} else {
echo '<h1>500 - Internal Server Error</h1>';
}
}
}
}

59
core/Auth.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
namespace Core;
class Auth
{
/**
* Check if user is authenticated
*/
public static function check(): bool
{
return isset($_SESSION['user_id']);
}
/**
* Get current user ID
*/
public static function id(): ?int
{
return $_SESSION['user_id'] ?? null;
}
/**
* Get current username
*/
public static function username(): ?string
{
return $_SESSION['username'] ?? null;
}
/**
* Get current user's full name
*/
public static function fullName(): ?string
{
return $_SESSION['full_name'] ?? null;
}
/**
* Require authentication (redirect to login if not authenticated)
*/
public static function require(): void
{
// Get current path
$currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
// Don't redirect if already on login page or logout
if ($currentPath === '/login' || $currentPath === '/logout') {
return;
}
if (!self::check()) {
$_SESSION['error'] = 'Please login to continue';
header('Location: /login');
exit;
}
}
}

33
core/Controller.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace Core;
abstract class Controller
{
protected function view(string $view, array $data = []): void
{
extract($data);
$viewPath = __DIR__ . "/../app/Views/$view.php";
if (!file_exists($viewPath)) {
throw new \Exception("View not found: $view");
}
require_once $viewPath;
}
protected function json($data, int $status = 200): void
{
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($data);
exit;
}
protected function redirect(string $path): void
{
header("Location: $path");
exit;
}
}

54
core/Database.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
namespace Core;
use PDO;
use PDOException;
class Database
{
private static ?PDO $pdo = null;
public function __construct()
{
if (self::$pdo === null) {
$this->connect();
}
}
private function connect()
{
$host = $_ENV['DB_HOST'];
$port = $_ENV['DB_PORT'];
$database = $_ENV['DB_DATABASE'];
$username = $_ENV['DB_USERNAME'];
$password = $_ENV['DB_PASSWORD'];
try {
$dsn = "mysql:host=$host;port=$port;dbname=$database;charset=utf8mb4";
self::$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
public static function getConnection(): PDO
{
if (self::$pdo === null) {
new self();
}
return self::$pdo;
}
public function query(string $sql, array $params = []): \PDOStatement
{
$stmt = self::$pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
}
}

65
core/Model.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
namespace Core;
use PDO;
abstract class Model
{
protected static string $table;
protected PDO $db;
public function __construct()
{
$this->db = Database::getConnection();
}
public function all(): array
{
$stmt = $this->db->query("SELECT * FROM " . static::$table . " ORDER BY id DESC");
return $stmt->fetchAll();
}
public function find(int $id): ?array
{
$stmt = $this->db->prepare("SELECT * FROM " . static::$table . " WHERE id = ?");
$stmt->execute([$id]);
$result = $stmt->fetch();
return $result ?: null;
}
public function create(array $data): int
{
$columns = implode(', ', array_keys($data));
$placeholders = implode(', ', array_fill(0, count($data), '?'));
$sql = "INSERT INTO " . static::$table . " ($columns) VALUES ($placeholders)";
$stmt = $this->db->prepare($sql);
$stmt->execute(array_values($data));
return (int)$this->db->lastInsertId();
}
public function update(int $id, array $data): bool
{
$set = implode(', ', array_map(fn($col) => "$col = ?", array_keys($data)));
$sql = "UPDATE " . static::$table . " SET $set WHERE id = ?";
$stmt = $this->db->prepare($sql);
return $stmt->execute([...array_values($data), $id]);
}
public function delete(int $id): bool
{
$stmt = $this->db->prepare("DELETE FROM " . static::$table . " WHERE id = ?");
return $stmt->execute([$id]);
}
public function where(string $column, $value): array
{
$stmt = $this->db->prepare("SELECT * FROM " . static::$table . " WHERE $column = ?");
$stmt->execute([$value]);
return $stmt->fetchAll();
}
}

76
core/Router.php Normal file
View File

@@ -0,0 +1,76 @@
<?php
namespace Core;
class Router
{
protected array $routes = [];
public function get(string $path, $callback)
{
$this->routes['GET'][$path] = $callback;
}
public function post(string $path, $callback)
{
$this->routes['POST'][$path] = $callback;
}
public function resolve()
{
$path = $_SERVER['REQUEST_URI'] ?? '/';
$method = $_SERVER['REQUEST_METHOD'];
// Remove query string
$position = strpos($path, '?');
if ($position !== false) {
$path = substr($path, 0, $position);
}
// Try exact match first
$callback = $this->routes[$method][$path] ?? null;
$params = [];
// If no exact match, try pattern matching for dynamic segments
if ($callback === null) {
foreach ($this->routes[$method] ?? [] as $route => $handler) {
// Convert route pattern to regex
$pattern = preg_replace('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', '([^/]+)', $route);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $path, $matches)) {
$callback = $handler;
// Extract parameter names from route
preg_match_all('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', $route, $paramNames);
// Map parameter names to values
array_shift($matches); // Remove full match
foreach ($paramNames[1] as $index => $name) {
$params[$name] = $matches[$index] ?? null;
}
break;
}
}
}
if ($callback === null) {
http_response_code(404);
require_once __DIR__ . '/../app/Views/errors/404.php';
return;
}
if (is_array($callback)) {
$controller = new $callback[0]();
$callback[0] = $controller;
}
// Pass params to the callback
if (!empty($params)) {
call_user_func($callback, $params);
} else {
call_user_func($callback);
}
}
}