<?php
namespace App\Controllers;
use App\Core\Auth;
use App\Core\Database;
use App\Core\Validator;
/**
* Base Controller
* Provides common functionality for all controllers
*/
class BaseController
{
/**
* @var mixed
*/
protected $db;
/**
* @var mixed
*/
protected $auth;
/**
* @var mixed
*/
protected $currentUser;
public function __construct()
{
// Start session if not already started
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$this->db = Database::getInstance();
$this->auth = new Auth($this->db, $_ENV['JWT_SECRET'] ?? 'default-secret');
// Set JSON content type by default
header('Content-Type: application/json');
// Handle CORS preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
$this->handleCorsPreflightRequest();
exit;
}
}
/**
* Send JSON response
*/
protected function jsonResponse(array $data, int $statusCode = 200): void
{
http_response_code($statusCode);
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit;
}
/**
* Get JSON input from request body
*/
protected function getJsonInput(): array
{
$input = file_get_contents('php://input');
$data = json_decode($input, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->jsonResponse([
'success' => false,
'error' => 'Invalid JSON input'
], 400);
}
return $data ?? [];
}
/**
* Get form input (POST data)
*/
protected function getFormInput(): array
{
return $_POST ?? [];
}
/**
* Get query parameters
*/
protected function getQueryParams(): array
{
return $_GET ?? [];
}
/**
* Require authentication
*/
protected function requireAuth(): bool
{
$token = $this->getBearerToken();
if (!$token) {
$this->jsonResponse([
'success' => false,
'error' => 'Authentication required',
'message' => 'No authentication token provided'
], 401);
return false;
}
$user = $this->auth->validateToken($token);
if (!$user) {
$this->jsonResponse([
'success' => false,
'error' => 'Invalid or expired token',
'message' => 'Please login again'
], 401);
return false;
}
$this->currentUser = $user;
return true;
}
/**
* Require admin privileges
*/
protected function requireAdmin(): bool
{
if (!$this->currentUser) {
$this->jsonResponse([
'success' => false,
'error' => 'Authentication required'
], 401);
return false;
}
if (!in_array($this->currentUser['role'], ['admin', 'staff'])) {
$this->jsonResponse([
'success' => false,
'error' => 'Admin privileges required',
'message' => 'You do not have permission to perform this action'
], 403);
return false;
}
return true;
}
/**
* Get current authenticated user
*/
protected function getCurrentUser(): ?array
{
return $_SESSION['authenticated_user'] ?? $this->currentUser;
}
/**
* Get Bearer token from Authorization header
*/
private function getBearerToken(): ?string
{
$headers = $this->getAuthorizationHeader();
if (!$headers) {
return null;
}
if (preg_match('/Bearer\s+(.*)$/i', $headers, $matches)) {
return $matches[1];
}
return null;
}
/**
* Get Authorization header
*/
private function getAuthorizationHeader(): ?string
{
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER['Authorization']);
} elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$headers = trim($_SERVER['HTTP_AUTHORIZATION']);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
$requestHeaders = array_combine(
array_map('ucwords', array_keys($requestHeaders)),
array_values($requestHeaders)
);
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
return $headers;
}
/**
* Validate input data
*/
protected function validateInput(array $data, array $rules): bool
{
$validator = new Validator();
return $validator->validate($data, $rules);
}
/**
* Get validation errors
*/
protected function getValidationErrors(array $data, array $rules): array
{
$validator = new Validator();
$validator->validate($data, $rules);
return $validator->getErrors();
}
/**
* Sanitize input data
*/
protected function sanitizeInput($input)
{
return Validator::sanitizeInput($input);
}
/**
* Check if debug mode is enabled
*/
protected function isDebugMode(): bool
{
return filter_var($_ENV['DEBUG'] ?? false, FILTER_VALIDATE_BOOLEAN);
}
/**
* Log error message
*/
protected function logError(string $message, array $context = []): void
{
$logMessage = $message;
if (!empty($context)) {
$logMessage .= ' Context: '.json_encode($context);
}
error_log($logMessage);
}
/**
* Handle CORS preflight requests
*/
private function handleCorsPreflightRequest(): void
{
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
http_response_code(200);
}
/**
* Set CORS headers for regular requests
*/
protected function setCorsHeaders(): void
{
$origin = $_SERVER['HTTP_ORIGIN'] ?? '*';
header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Expose-Headers: Content-Length, X-JSON');
}
/**
* Handle method override for POST requests
*/
protected function getRequestMethod(): string
{
$method = $_SERVER['REQUEST_METHOD'];
// Handle method override for POST requests
if ($method === 'POST' && isset($_POST['_method'])) {
$overrideMethod = strtoupper($_POST['_method']);
if (in_array($overrideMethod, ['PUT', 'PATCH', 'DELETE'])) {
return $overrideMethod;
}
}
return $method;
}
/**
* Get request data based on method
*/
protected function getRequestData(): array
{
$method = $this->getRequestMethod();
switch ($method) {
case 'GET':
return $_GET;
case 'POST':
return array_merge($_POST, $this->getJsonInput());
case 'PUT':
case 'PATCH':
case 'DELETE':
return $this->getJsonInput();
default:
return [];
}
}
/**
* Check if request is AJAX
*/
protected function isAjaxRequest(): bool
{
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}
/**
* Get client IP address
*/
protected function getClientIp(): string
{
$ipKeys = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
foreach ($ipKeys as $key) {
if (!empty($_SERVER[$key])) {
$ip = $_SERVER[$key];
if (strpos($ip, ',') !== false) {
$ip = trim(explode(',', $ip)[0]);
}
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
/**
* Get user agent
*/
protected function getUserAgent(): string
{
return $_SERVER['HTTP_USER_AGENT'] ?? '';
}
/**
* Get request parameters (GET, POST, or JSON body)
*/
protected function getRequestParams(): array
{
$params = [];
// Merge GET parameters
$params = array_merge($params, $_GET);
// Merge POST parameters
$params = array_merge($params, $_POST);
// Parse JSON body if content type is JSON
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (strpos($contentType, 'application/json') !== false) {
$jsonBody = json_decode(file_get_contents('php://input'), true);
if (is_array($jsonBody)) {
$params = array_merge($params, $jsonBody);
}
}
return $params;
}
/**
* Get request body (JSON or POST data)
*/
protected function getRequestBody(): array
{
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (strpos($contentType, 'application/json') !== false) {
$jsonBody = json_decode(file_get_contents('php://input'), true);
return is_array($jsonBody) ? $jsonBody : [];
}
return $_POST;
}
}