Add PHP 7.4+ type hints to helpers, models, and middleware
Added strict typing with parameter types, return types, and property types across all core classes: - helpers: Database, ErrorHandler, CacheHelper - models: TicketModel, UserModel, WorkflowModel, TemplateModel, UserPreferencesModel - middleware: RateLimitMiddleware, CsrfMiddleware, SecurityHeadersMiddleware Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,15 +6,15 @@
|
|||||||
* such as workflow rules, user preferences, and configuration data.
|
* such as workflow rules, user preferences, and configuration data.
|
||||||
*/
|
*/
|
||||||
class CacheHelper {
|
class CacheHelper {
|
||||||
private static $cacheDir = null;
|
private static ?string $cacheDir = null;
|
||||||
private static $memoryCache = [];
|
private static array $memoryCache = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the cache directory path
|
* Get the cache directory path
|
||||||
*
|
*
|
||||||
* @return string Cache directory path
|
* @return string Cache directory path
|
||||||
*/
|
*/
|
||||||
private static function getCacheDir() {
|
private static function getCacheDir(): string {
|
||||||
if (self::$cacheDir === null) {
|
if (self::$cacheDir === null) {
|
||||||
self::$cacheDir = sys_get_temp_dir() . '/tinker_tickets_cache';
|
self::$cacheDir = sys_get_temp_dir() . '/tinker_tickets_cache';
|
||||||
if (!is_dir(self::$cacheDir)) {
|
if (!is_dir(self::$cacheDir)) {
|
||||||
@@ -31,7 +31,7 @@ class CacheHelper {
|
|||||||
* @param mixed $identifier Unique identifier
|
* @param mixed $identifier Unique identifier
|
||||||
* @return string Cache key
|
* @return string Cache key
|
||||||
*/
|
*/
|
||||||
private static function makeKey($prefix, $identifier = null) {
|
private static function makeKey(string $prefix, $identifier = null): string {
|
||||||
$key = $prefix;
|
$key = $prefix;
|
||||||
if ($identifier !== null) {
|
if ($identifier !== null) {
|
||||||
$key .= '_' . md5(serialize($identifier));
|
$key .= '_' . md5(serialize($identifier));
|
||||||
@@ -47,7 +47,7 @@ class CacheHelper {
|
|||||||
* @param int $ttl Time-to-live in seconds (default 300 = 5 minutes)
|
* @param int $ttl Time-to-live in seconds (default 300 = 5 minutes)
|
||||||
* @return mixed|null Cached data or null if not found/expired
|
* @return mixed|null Cached data or null if not found/expired
|
||||||
*/
|
*/
|
||||||
public static function get($prefix, $identifier = null, $ttl = 300) {
|
public static function get(string $prefix, $identifier = null, int $ttl = 300) {
|
||||||
$key = self::makeKey($prefix, $identifier);
|
$key = self::makeKey($prefix, $identifier);
|
||||||
|
|
||||||
// Check memory cache first (fastest)
|
// Check memory cache first (fastest)
|
||||||
@@ -88,7 +88,7 @@ class CacheHelper {
|
|||||||
* @param mixed $data Data to cache
|
* @param mixed $data Data to cache
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
public static function set($prefix, $identifier, $data) {
|
public static function set(string $prefix, $identifier, $data): bool {
|
||||||
$key = self::makeKey($prefix, $identifier);
|
$key = self::makeKey($prefix, $identifier);
|
||||||
$cached = [
|
$cached = [
|
||||||
'time' => time(),
|
'time' => time(),
|
||||||
@@ -110,7 +110,7 @@ class CacheHelper {
|
|||||||
* @param mixed $identifier Unique identifier (null to delete all with prefix)
|
* @param mixed $identifier Unique identifier (null to delete all with prefix)
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
public static function delete($prefix, $identifier = null) {
|
public static function delete(string $prefix, $identifier = null): bool {
|
||||||
if ($identifier !== null) {
|
if ($identifier !== null) {
|
||||||
$key = self::makeKey($prefix, $identifier);
|
$key = self::makeKey($prefix, $identifier);
|
||||||
unset(self::$memoryCache[$key]);
|
unset(self::$memoryCache[$key]);
|
||||||
@@ -140,7 +140,7 @@ class CacheHelper {
|
|||||||
*
|
*
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
public static function clearAll() {
|
public static function clearAll(): bool {
|
||||||
self::$memoryCache = [];
|
self::$memoryCache = [];
|
||||||
|
|
||||||
$files = glob(self::getCacheDir() . '/*.json');
|
$files = glob(self::getCacheDir() . '/*.json');
|
||||||
@@ -160,7 +160,7 @@ class CacheHelper {
|
|||||||
* @param int $ttl Time-to-live in seconds
|
* @param int $ttl Time-to-live in seconds
|
||||||
* @return mixed Cached or freshly fetched data
|
* @return mixed Cached or freshly fetched data
|
||||||
*/
|
*/
|
||||||
public static function remember($prefix, $identifier, $callback, $ttl = 300) {
|
public static function remember(string $prefix, $identifier, callable $callback, int $ttl = 300) {
|
||||||
$data = self::get($prefix, $identifier, $ttl);
|
$data = self::get($prefix, $identifier, $ttl);
|
||||||
|
|
||||||
if ($data === null) {
|
if ($data === null) {
|
||||||
@@ -178,7 +178,7 @@ class CacheHelper {
|
|||||||
*
|
*
|
||||||
* @param int $maxAge Maximum age in seconds (default 1 hour)
|
* @param int $maxAge Maximum age in seconds (default 1 hour)
|
||||||
*/
|
*/
|
||||||
public static function cleanup($maxAge = 3600) {
|
public static function cleanup(int $maxAge = 3600): void {
|
||||||
$files = glob(self::getCacheDir() . '/*.json');
|
$files = glob(self::getCacheDir() . '/*.json');
|
||||||
$now = time();
|
$now = time();
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* Provides a singleton connection for the request lifecycle.
|
* Provides a singleton connection for the request lifecycle.
|
||||||
*/
|
*/
|
||||||
class Database {
|
class Database {
|
||||||
private static $connection = null;
|
private static ?mysqli $connection = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get database connection (singleton pattern)
|
* Get database connection (singleton pattern)
|
||||||
@@ -14,7 +14,7 @@ class Database {
|
|||||||
* @return mysqli Database connection
|
* @return mysqli Database connection
|
||||||
* @throws Exception If connection fails
|
* @throws Exception If connection fails
|
||||||
*/
|
*/
|
||||||
public static function getConnection() {
|
public static function getConnection(): mysqli {
|
||||||
if (self::$connection === null) {
|
if (self::$connection === null) {
|
||||||
self::$connection = self::createConnection();
|
self::$connection = self::createConnection();
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ class Database {
|
|||||||
* @return mysqli Database connection
|
* @return mysqli Database connection
|
||||||
* @throws Exception If connection fails
|
* @throws Exception If connection fails
|
||||||
*/
|
*/
|
||||||
private static function createConnection() {
|
private static function createConnection(): mysqli {
|
||||||
// Ensure config is loaded
|
// Ensure config is loaded
|
||||||
if (!isset($GLOBALS['config'])) {
|
if (!isset($GLOBALS['config'])) {
|
||||||
require_once dirname(__DIR__) . '/config/config.php';
|
require_once dirname(__DIR__) . '/config/config.php';
|
||||||
@@ -59,7 +59,7 @@ class Database {
|
|||||||
/**
|
/**
|
||||||
* Close the database connection
|
* Close the database connection
|
||||||
*/
|
*/
|
||||||
public static function close() {
|
public static function close(): void {
|
||||||
if (self::$connection !== null) {
|
if (self::$connection !== null) {
|
||||||
self::$connection->close();
|
self::$connection->close();
|
||||||
self::$connection = null;
|
self::$connection = null;
|
||||||
@@ -71,7 +71,7 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
public static function beginTransaction() {
|
public static function beginTransaction(): bool {
|
||||||
return self::getConnection()->begin_transaction();
|
return self::getConnection()->begin_transaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
public static function commit() {
|
public static function commit(): bool {
|
||||||
return self::getConnection()->commit();
|
return self::getConnection()->commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
public static function rollback() {
|
public static function rollback(): bool {
|
||||||
return self::getConnection()->rollback();
|
return self::getConnection()->rollback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class Database {
|
|||||||
* @param array $params Parameters to bind
|
* @param array $params Parameters to bind
|
||||||
* @return mysqli_result|bool Query result
|
* @return mysqli_result|bool Query result
|
||||||
*/
|
*/
|
||||||
public static function query($sql, $types = '', $params = []) {
|
public static function query(string $sql, string $types = '', array $params = []) {
|
||||||
$conn = self::getConnection();
|
$conn = self::getConnection();
|
||||||
|
|
||||||
if (empty($types) || empty($params)) {
|
if (empty($types) || empty($params)) {
|
||||||
@@ -130,7 +130,7 @@ class Database {
|
|||||||
* @param array $params Parameters to bind
|
* @param array $params Parameters to bind
|
||||||
* @return int Affected rows (-1 on failure)
|
* @return int Affected rows (-1 on failure)
|
||||||
*/
|
*/
|
||||||
public static function execute($sql, $types = '', $params = []) {
|
public static function execute(string $sql, string $types = '', array $params = []): int {
|
||||||
$conn = self::getConnection();
|
$conn = self::getConnection();
|
||||||
|
|
||||||
$stmt = $conn->prepare($sql);
|
$stmt = $conn->prepare($sql);
|
||||||
@@ -158,7 +158,7 @@ class Database {
|
|||||||
*
|
*
|
||||||
* @return int Last insert ID
|
* @return int Last insert ID
|
||||||
*/
|
*/
|
||||||
public static function lastInsertId() {
|
public static function lastInsertId(): int {
|
||||||
return self::getConnection()->insert_id;
|
return self::getConnection()->insert_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ class Database {
|
|||||||
* @param string $string String to escape
|
* @param string $string String to escape
|
||||||
* @return string Escaped string
|
* @return string Escaped string
|
||||||
*/
|
*/
|
||||||
public static function escape($string) {
|
public static function escape(string $string): string {
|
||||||
return self::getConnection()->real_escape_string($string);
|
return self::getConnection()->real_escape_string($string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@
|
|||||||
* across the application.
|
* across the application.
|
||||||
*/
|
*/
|
||||||
class ErrorHandler {
|
class ErrorHandler {
|
||||||
private static $logFile = null;
|
private static ?string $logFile = null;
|
||||||
private static $initialized = false;
|
private static bool $initialized = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize error handling
|
* Initialize error handling
|
||||||
*
|
*
|
||||||
* @param bool $displayErrors Whether to display errors (false in production)
|
* @param bool $displayErrors Whether to display errors (false in production)
|
||||||
*/
|
*/
|
||||||
public static function init($displayErrors = false) {
|
public static function init(bool $displayErrors = false): void {
|
||||||
if (self::$initialized) {
|
if (self::$initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ class ErrorHandler {
|
|||||||
* @param int $errline Line number
|
* @param int $errline Line number
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function handleError($errno, $errstr, $errfile, $errline) {
|
public static function handleError(int $errno, string $errstr, string $errfile, int $errline): bool {
|
||||||
// Don't handle suppressed errors
|
// Don't handle suppressed errors
|
||||||
if (!(error_reporting() & $errno)) {
|
if (!(error_reporting() & $errno)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -69,7 +69,7 @@ class ErrorHandler {
|
|||||||
*
|
*
|
||||||
* @param Throwable $exception
|
* @param Throwable $exception
|
||||||
*/
|
*/
|
||||||
public static function handleException($exception) {
|
public static function handleException(Throwable $exception): void {
|
||||||
$message = sprintf(
|
$message = sprintf(
|
||||||
"Uncaught %s: %s in %s on line %d\nStack trace:\n%s",
|
"Uncaught %s: %s in %s on line %d\nStack trace:\n%s",
|
||||||
get_class($exception),
|
get_class($exception),
|
||||||
@@ -94,7 +94,7 @@ class ErrorHandler {
|
|||||||
/**
|
/**
|
||||||
* Handle fatal errors on shutdown
|
* Handle fatal errors on shutdown
|
||||||
*/
|
*/
|
||||||
public static function handleShutdown() {
|
public static function handleShutdown(): void {
|
||||||
$error = error_get_last();
|
$error = error_get_last();
|
||||||
|
|
||||||
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
|
||||||
@@ -120,7 +120,7 @@ class ErrorHandler {
|
|||||||
* @param int $level Error level
|
* @param int $level Error level
|
||||||
* @param array $context Additional context
|
* @param array $context Additional context
|
||||||
*/
|
*/
|
||||||
public static function log($message, $level = E_USER_NOTICE, $context = []) {
|
public static function log(string $message, int $level = E_USER_NOTICE, array $context = []): void {
|
||||||
$timestamp = date('Y-m-d H:i:s');
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
$levelName = self::getErrorTypeName($level);
|
$levelName = self::getErrorTypeName($level);
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ class ErrorHandler {
|
|||||||
* @param int $httpCode HTTP status code
|
* @param int $httpCode HTTP status code
|
||||||
* @param Throwable|null $exception Original exception (for debug info)
|
* @param Throwable|null $exception Original exception (for debug info)
|
||||||
*/
|
*/
|
||||||
public static function sendErrorResponse($message, $httpCode = 500, $exception = null) {
|
public static function sendErrorResponse(string $message, int $httpCode = 500, ?Throwable $exception = null): void {
|
||||||
http_response_code($httpCode);
|
http_response_code($httpCode);
|
||||||
|
|
||||||
if (!headers_sent()) {
|
if (!headers_sent()) {
|
||||||
@@ -172,7 +172,7 @@ class ErrorHandler {
|
|||||||
* @param array $errors Array of validation errors
|
* @param array $errors Array of validation errors
|
||||||
* @param string $message Overall error message
|
* @param string $message Overall error message
|
||||||
*/
|
*/
|
||||||
public static function sendValidationError($errors, $message = 'Validation failed') {
|
public static function sendValidationError(array $errors, string $message = 'Validation failed'): void {
|
||||||
http_response_code(422);
|
http_response_code(422);
|
||||||
|
|
||||||
if (!headers_sent()) {
|
if (!headers_sent()) {
|
||||||
@@ -192,7 +192,7 @@ class ErrorHandler {
|
|||||||
*
|
*
|
||||||
* @param string $message Error message
|
* @param string $message Error message
|
||||||
*/
|
*/
|
||||||
public static function sendNotFoundError($message = 'Resource not found') {
|
public static function sendNotFoundError(string $message = 'Resource not found'): void {
|
||||||
self::sendErrorResponse($message, 404);
|
self::sendErrorResponse($message, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ class ErrorHandler {
|
|||||||
*
|
*
|
||||||
* @param string $message Error message
|
* @param string $message Error message
|
||||||
*/
|
*/
|
||||||
public static function sendUnauthorizedError($message = 'Authentication required') {
|
public static function sendUnauthorizedError(string $message = 'Authentication required'): void {
|
||||||
self::sendErrorResponse($message, 401);
|
self::sendErrorResponse($message, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ class ErrorHandler {
|
|||||||
*
|
*
|
||||||
* @param string $message Error message
|
* @param string $message Error message
|
||||||
*/
|
*/
|
||||||
public static function sendForbiddenError($message = 'Access denied') {
|
public static function sendForbiddenError(string $message = 'Access denied'): void {
|
||||||
self::sendErrorResponse($message, 403);
|
self::sendErrorResponse($message, 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ class ErrorHandler {
|
|||||||
* @param int $errno Error number
|
* @param int $errno Error number
|
||||||
* @return string Error type name
|
* @return string Error type name
|
||||||
*/
|
*/
|
||||||
private static function getErrorTypeName($errno) {
|
private static function getErrorTypeName(int $errno): string {
|
||||||
$types = [
|
$types = [
|
||||||
E_ERROR => 'ERROR',
|
E_ERROR => 'ERROR',
|
||||||
E_WARNING => 'WARNING',
|
E_WARNING => 'WARNING',
|
||||||
@@ -248,7 +248,7 @@ class ErrorHandler {
|
|||||||
* @param int $lines Number of lines to return
|
* @param int $lines Number of lines to return
|
||||||
* @return array Log entries
|
* @return array Log entries
|
||||||
*/
|
*/
|
||||||
public static function getRecentErrors($lines = 50) {
|
public static function getRecentErrors(int $lines = 50): array {
|
||||||
if (self::$logFile === null || !file_exists(self::$logFile)) {
|
if (self::$logFile === null || !file_exists(self::$logFile)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
* Generates and validates CSRF tokens for all state-changing operations
|
* Generates and validates CSRF tokens for all state-changing operations
|
||||||
*/
|
*/
|
||||||
class CsrfMiddleware {
|
class CsrfMiddleware {
|
||||||
private static $tokenName = 'csrf_token';
|
private static string $tokenName = 'csrf_token';
|
||||||
private static $tokenTime = 'csrf_token_time';
|
private static string $tokenTime = 'csrf_token_time';
|
||||||
private static $tokenLifetime = 3600; // 1 hour
|
private static int $tokenLifetime = 3600; // 1 hour
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a new CSRF token
|
* Generate a new CSRF token
|
||||||
*/
|
*/
|
||||||
public static function generateToken() {
|
public static function generateToken(): string {
|
||||||
$_SESSION[self::$tokenName] = bin2hex(random_bytes(32));
|
$_SESSION[self::$tokenName] = bin2hex(random_bytes(32));
|
||||||
$_SESSION[self::$tokenTime] = time();
|
$_SESSION[self::$tokenTime] = time();
|
||||||
return $_SESSION[self::$tokenName];
|
return $_SESSION[self::$tokenName];
|
||||||
@@ -20,7 +20,7 @@ class CsrfMiddleware {
|
|||||||
/**
|
/**
|
||||||
* Get current CSRF token, regenerate if expired
|
* Get current CSRF token, regenerate if expired
|
||||||
*/
|
*/
|
||||||
public static function getToken() {
|
public static function getToken(): string {
|
||||||
if (!isset($_SESSION[self::$tokenName]) || self::isTokenExpired()) {
|
if (!isset($_SESSION[self::$tokenName]) || self::isTokenExpired()) {
|
||||||
return self::generateToken();
|
return self::generateToken();
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ class CsrfMiddleware {
|
|||||||
/**
|
/**
|
||||||
* Validate CSRF token (constant-time comparison)
|
* Validate CSRF token (constant-time comparison)
|
||||||
*/
|
*/
|
||||||
public static function validateToken($token) {
|
public static function validateToken(string $token): bool {
|
||||||
if (!isset($_SESSION[self::$tokenName])) {
|
if (!isset($_SESSION[self::$tokenName])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -47,9 +47,8 @@ class CsrfMiddleware {
|
|||||||
/**
|
/**
|
||||||
* Check if token is expired
|
* Check if token is expired
|
||||||
*/
|
*/
|
||||||
private static function isTokenExpired() {
|
private static function isTokenExpired(): bool {
|
||||||
return !isset($_SESSION[self::$tokenTime]) ||
|
return !isset($_SESSION[self::$tokenTime]) ||
|
||||||
(time() - $_SESSION[self::$tokenTime]) > self::$tokenLifetime;
|
(time() - $_SESSION[self::$tokenTime]) > self::$tokenLifetime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
|
||||||
|
|||||||
@@ -7,21 +7,21 @@
|
|||||||
*/
|
*/
|
||||||
class RateLimitMiddleware {
|
class RateLimitMiddleware {
|
||||||
// Default limits
|
// Default limits
|
||||||
const DEFAULT_LIMIT = 100; // requests per window (session)
|
public const DEFAULT_LIMIT = 100; // requests per window (session)
|
||||||
const API_LIMIT = 60; // API requests per window (session)
|
public const API_LIMIT = 60; // API requests per window (session)
|
||||||
const IP_LIMIT = 300; // IP-based requests per window (more generous)
|
public const IP_LIMIT = 300; // IP-based requests per window (more generous)
|
||||||
const IP_API_LIMIT = 120; // IP-based API requests per window
|
public const IP_API_LIMIT = 120; // IP-based API requests per window
|
||||||
const WINDOW_SECONDS = 60; // 1 minute window
|
public const WINDOW_SECONDS = 60; // 1 minute window
|
||||||
|
|
||||||
// Directory for IP rate limit storage
|
// Directory for IP rate limit storage
|
||||||
private static $rateLimitDir = null;
|
private static ?string $rateLimitDir = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the rate limit storage directory
|
* Get the rate limit storage directory
|
||||||
*
|
*
|
||||||
* @return string Path to rate limit storage directory
|
* @return string Path to rate limit storage directory
|
||||||
*/
|
*/
|
||||||
private static function getRateLimitDir() {
|
private static function getRateLimitDir(): string {
|
||||||
if (self::$rateLimitDir === null) {
|
if (self::$rateLimitDir === null) {
|
||||||
self::$rateLimitDir = sys_get_temp_dir() . '/tinker_tickets_ratelimit';
|
self::$rateLimitDir = sys_get_temp_dir() . '/tinker_tickets_ratelimit';
|
||||||
if (!is_dir(self::$rateLimitDir)) {
|
if (!is_dir(self::$rateLimitDir)) {
|
||||||
@@ -36,7 +36,7 @@ class RateLimitMiddleware {
|
|||||||
*
|
*
|
||||||
* @return string Client IP address
|
* @return string Client IP address
|
||||||
*/
|
*/
|
||||||
private static function getClientIp() {
|
private static function getClientIp(): string {
|
||||||
// Check for forwarded IP (behind proxy/load balancer)
|
// Check for forwarded IP (behind proxy/load balancer)
|
||||||
$headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
|
$headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP'];
|
||||||
foreach ($headers as $header) {
|
foreach ($headers as $header) {
|
||||||
@@ -58,7 +58,7 @@ class RateLimitMiddleware {
|
|||||||
* @param string $type 'default' or 'api'
|
* @param string $type 'default' or 'api'
|
||||||
* @return bool True if request is allowed, false if rate limited
|
* @return bool True if request is allowed, false if rate limited
|
||||||
*/
|
*/
|
||||||
private static function checkIpRateLimit($type = 'default') {
|
private static function checkIpRateLimit(string $type = 'default'): bool {
|
||||||
$ip = self::getClientIp();
|
$ip = self::getClientIp();
|
||||||
$limit = $type === 'api' ? self::IP_API_LIMIT : self::IP_LIMIT;
|
$limit = $type === 'api' ? self::IP_API_LIMIT : self::IP_LIMIT;
|
||||||
$now = time();
|
$now = time();
|
||||||
@@ -97,7 +97,7 @@ class RateLimitMiddleware {
|
|||||||
/**
|
/**
|
||||||
* Clean up old rate limit files (call periodically)
|
* Clean up old rate limit files (call periodically)
|
||||||
*/
|
*/
|
||||||
public static function cleanupOldFiles() {
|
public static function cleanupOldFiles(): void {
|
||||||
$dir = self::getRateLimitDir();
|
$dir = self::getRateLimitDir();
|
||||||
$files = glob($dir . '/*.json');
|
$files = glob($dir . '/*.json');
|
||||||
$now = time();
|
$now = time();
|
||||||
@@ -116,7 +116,7 @@ class RateLimitMiddleware {
|
|||||||
* @param string $type 'default' or 'api'
|
* @param string $type 'default' or 'api'
|
||||||
* @return bool True if request is allowed, false if rate limited
|
* @return bool True if request is allowed, false if rate limited
|
||||||
*/
|
*/
|
||||||
public static function check($type = 'default') {
|
public static function check(string $type = 'default'): bool {
|
||||||
// First check IP-based rate limit (prevents session bypass)
|
// First check IP-based rate limit (prevents session bypass)
|
||||||
if (!self::checkIpRateLimit($type)) {
|
if (!self::checkIpRateLimit($type)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -164,7 +164,7 @@ class RateLimitMiddleware {
|
|||||||
*
|
*
|
||||||
* @param string $type 'default' or 'api'
|
* @param string $type 'default' or 'api'
|
||||||
*/
|
*/
|
||||||
public static function apply($type = 'default') {
|
public static function apply(string $type = 'default'): void {
|
||||||
// Periodically clean up old rate limit files (1% chance per request)
|
// Periodically clean up old rate limit files (1% chance per request)
|
||||||
if (mt_rand(1, 100) === 1) {
|
if (mt_rand(1, 100) === 1) {
|
||||||
self::cleanupOldFiles();
|
self::cleanupOldFiles();
|
||||||
@@ -189,7 +189,7 @@ class RateLimitMiddleware {
|
|||||||
* @param string $type 'default' or 'api'
|
* @param string $type 'default' or 'api'
|
||||||
* @return array Rate limit status
|
* @return array Rate limit status
|
||||||
*/
|
*/
|
||||||
public static function getStatus($type = 'default') {
|
public static function getStatus(string $type = 'default'): array {
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ class RateLimitMiddleware {
|
|||||||
*
|
*
|
||||||
* @param string $type 'default' or 'api'
|
* @param string $type 'default' or 'api'
|
||||||
*/
|
*/
|
||||||
public static function addHeaders($type = 'default') {
|
public static function addHeaders(string $type = 'default'): void {
|
||||||
$status = self::getStatus($type);
|
$status = self::getStatus($type);
|
||||||
header('X-RateLimit-Limit: ' . $status['limit']);
|
header('X-RateLimit-Limit: ' . $status['limit']);
|
||||||
header('X-RateLimit-Remaining: ' . $status['remaining']);
|
header('X-RateLimit-Remaining: ' . $status['remaining']);
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
* Applies security-related HTTP headers to all responses.
|
* Applies security-related HTTP headers to all responses.
|
||||||
*/
|
*/
|
||||||
class SecurityHeadersMiddleware {
|
class SecurityHeadersMiddleware {
|
||||||
private static $nonce = null;
|
private static ?string $nonce = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate or retrieve the CSP nonce for this request
|
* Generate or retrieve the CSP nonce for this request
|
||||||
*
|
*
|
||||||
* @return string The nonce value
|
* @return string The nonce value
|
||||||
*/
|
*/
|
||||||
public static function getNonce() {
|
public static function getNonce(): string {
|
||||||
if (self::$nonce === null) {
|
if (self::$nonce === null) {
|
||||||
self::$nonce = base64_encode(random_bytes(16));
|
self::$nonce = base64_encode(random_bytes(16));
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ class SecurityHeadersMiddleware {
|
|||||||
/**
|
/**
|
||||||
* Apply security headers to the response
|
* Apply security headers to the response
|
||||||
*/
|
*/
|
||||||
public static function apply() {
|
public static function apply(): void {
|
||||||
$nonce = self::getNonce();
|
$nonce = self::getNonce();
|
||||||
|
|
||||||
// Content Security Policy - restricts where resources can be loaded from
|
// Content Security Policy - restricts where resources can be loaded from
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
* TemplateModel - Handles ticket template operations
|
* TemplateModel - Handles ticket template operations
|
||||||
*/
|
*/
|
||||||
class TemplateModel {
|
class TemplateModel {
|
||||||
private $conn;
|
private mysqli $conn;
|
||||||
|
|
||||||
public function __construct($conn) {
|
public function __construct(mysqli $conn) {
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ class TemplateModel {
|
|||||||
*
|
*
|
||||||
* @return array Array of template records
|
* @return array Array of template records
|
||||||
*/
|
*/
|
||||||
public function getAllTemplates() {
|
public function getAllTemplates(): array {
|
||||||
$sql = "SELECT * FROM ticket_templates WHERE is_active = TRUE ORDER BY template_name";
|
$sql = "SELECT * FROM ticket_templates WHERE is_active = TRUE ORDER BY template_name";
|
||||||
$result = $this->conn->query($sql);
|
$result = $this->conn->query($sql);
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ class TemplateModel {
|
|||||||
* @param int $templateId Template ID
|
* @param int $templateId Template ID
|
||||||
* @return array|null Template record or null if not found
|
* @return array|null Template record or null if not found
|
||||||
*/
|
*/
|
||||||
public function getTemplateById($templateId) {
|
public function getTemplateById(int $templateId): ?array {
|
||||||
$sql = "SELECT * FROM ticket_templates WHERE template_id = ? AND is_active = TRUE";
|
$sql = "SELECT * FROM ticket_templates WHERE template_id = ? AND is_active = TRUE";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
$stmt->bind_param("i", $templateId);
|
$stmt->bind_param("i", $templateId);
|
||||||
@@ -50,7 +50,7 @@ class TemplateModel {
|
|||||||
* @param int $createdBy User ID creating the template
|
* @param int $createdBy User ID creating the template
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function createTemplate($data, $createdBy) {
|
public function createTemplate(array $data, int $createdBy): bool {
|
||||||
$sql = "INSERT INTO ticket_templates (template_name, title_template, description_template,
|
$sql = "INSERT INTO ticket_templates (template_name, title_template, description_template,
|
||||||
category, type, default_priority, created_by)
|
category, type, default_priority, created_by)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)";
|
VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||||
@@ -77,7 +77,7 @@ class TemplateModel {
|
|||||||
* @param array $data Template data to update
|
* @param array $data Template data to update
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function updateTemplate($templateId, $data) {
|
public function updateTemplate(int $templateId, array $data): bool {
|
||||||
$sql = "UPDATE ticket_templates SET
|
$sql = "UPDATE ticket_templates SET
|
||||||
template_name = ?,
|
template_name = ?,
|
||||||
title_template = ?,
|
title_template = ?,
|
||||||
@@ -108,7 +108,7 @@ class TemplateModel {
|
|||||||
* @param int $templateId Template ID
|
* @param int $templateId Template ID
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function deactivateTemplate($templateId) {
|
public function deactivateTemplate(int $templateId): bool {
|
||||||
$sql = "UPDATE ticket_templates SET is_active = FALSE WHERE template_id = ?";
|
$sql = "UPDATE ticket_templates SET is_active = FALSE WHERE template_id = ?";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
$stmt->bind_param("i", $templateId);
|
$stmt->bind_param("i", $templateId);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
class TicketModel {
|
class TicketModel {
|
||||||
private $conn;
|
private mysqli $conn;
|
||||||
|
|
||||||
public function __construct($conn) {
|
public function __construct(mysqli $conn) {
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTicketById($id) {
|
public function getTicketById(int $id): ?array {
|
||||||
$sql = "SELECT t.*,
|
$sql = "SELECT t.*,
|
||||||
u_created.username as creator_username,
|
u_created.username as creator_username,
|
||||||
u_created.display_name as creator_display_name,
|
u_created.display_name as creator_display_name,
|
||||||
@@ -31,7 +31,7 @@ class TicketModel {
|
|||||||
return $result->fetch_assoc();
|
return $result->fetch_assoc();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTicketComments($ticketId) {
|
public function getTicketComments(int $ticketId): array {
|
||||||
$sql = "SELECT * FROM ticket_comments WHERE ticket_id = ? ORDER BY created_at DESC";
|
$sql = "SELECT * FROM ticket_comments WHERE ticket_id = ? ORDER BY created_at DESC";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
$stmt->bind_param("i", $ticketId);
|
$stmt->bind_param("i", $ticketId);
|
||||||
@@ -46,7 +46,7 @@ class TicketModel {
|
|||||||
return $comments;
|
return $comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllTickets($page = 1, $limit = 15, $status = 'Open', $sortColumn = 'ticket_id', $sortDirection = 'desc', $category = null, $type = null, $search = null, $filters = []) {
|
public function getAllTickets(int $page = 1, int $limit = 15, ?string $status = 'Open', string $sortColumn = 'ticket_id', string $sortDirection = 'desc', ?string $category = null, ?string $type = null, ?string $search = null, array $filters = []): array {
|
||||||
// Calculate offset
|
// Calculate offset
|
||||||
$offset = ($page - 1) * $limit;
|
$offset = ($page - 1) * $limit;
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ class TicketModel {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTicket($ticketData, $updatedBy = null) {
|
public function updateTicket(array $ticketData, ?int $updatedBy = null): bool {
|
||||||
$sql = "UPDATE tickets SET
|
$sql = "UPDATE tickets SET
|
||||||
title = ?,
|
title = ?,
|
||||||
priority = ?,
|
priority = ?,
|
||||||
@@ -257,7 +257,7 @@ class TicketModel {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTicket($ticketData, $createdBy = null) {
|
public function createTicket(array $ticketData, ?int $createdBy = null): array {
|
||||||
// Generate unique ticket ID (9-digit format with leading zeros)
|
// Generate unique ticket ID (9-digit format with leading zeros)
|
||||||
// Loop until we find an ID that doesn't exist to prevent collisions
|
// Loop until we find an ID that doesn't exist to prevent collisions
|
||||||
$maxAttempts = 10;
|
$maxAttempts = 10;
|
||||||
@@ -347,7 +347,7 @@ class TicketModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addComment($ticketId, $commentData) {
|
public function addComment(int $ticketId, array $commentData): array {
|
||||||
$sql = "INSERT INTO ticket_comments (ticket_id, user_name, comment_text, markdown_enabled)
|
$sql = "INSERT INTO ticket_comments (ticket_id, user_name, comment_text, markdown_enabled)
|
||||||
VALUES (?, ?, ?, ?)";
|
VALUES (?, ?, ?, ?)";
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ class TicketModel {
|
|||||||
* @param int $assignedBy User ID performing the assignment
|
* @param int $assignedBy User ID performing the assignment
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function assignTicket($ticketId, $userId, $assignedBy) {
|
public function assignTicket(int $ticketId, int $userId, int $assignedBy): bool {
|
||||||
$sql = "UPDATE tickets SET assigned_to = ?, updated_by = ?, updated_at = NOW() WHERE ticket_id = ?";
|
$sql = "UPDATE tickets SET assigned_to = ?, updated_by = ?, updated_at = NOW() WHERE ticket_id = ?";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
$stmt->bind_param("iii", $userId, $assignedBy, $ticketId);
|
$stmt->bind_param("iii", $userId, $assignedBy, $ticketId);
|
||||||
@@ -403,7 +403,7 @@ class TicketModel {
|
|||||||
* @param int $updatedBy User ID performing the unassignment
|
* @param int $updatedBy User ID performing the unassignment
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function unassignTicket($ticketId, $updatedBy) {
|
public function unassignTicket(int $ticketId, int $updatedBy): bool {
|
||||||
$sql = "UPDATE tickets SET assigned_to = NULL, updated_by = ?, updated_at = NOW() WHERE ticket_id = ?";
|
$sql = "UPDATE tickets SET assigned_to = NULL, updated_by = ?, updated_at = NOW() WHERE ticket_id = ?";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
$stmt->bind_param("ii", $updatedBy, $ticketId);
|
$stmt->bind_param("ii", $updatedBy, $ticketId);
|
||||||
@@ -419,7 +419,7 @@ class TicketModel {
|
|||||||
* @param array $ticketIds Array of ticket IDs
|
* @param array $ticketIds Array of ticket IDs
|
||||||
* @return array Associative array keyed by ticket_id
|
* @return array Associative array keyed by ticket_id
|
||||||
*/
|
*/
|
||||||
public function getTicketsByIds($ticketIds) {
|
public function getTicketsByIds(array $ticketIds): array {
|
||||||
if (empty($ticketIds)) {
|
if (empty($ticketIds)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -465,7 +465,7 @@ class TicketModel {
|
|||||||
* @param array $user The user data (must include user_id, is_admin, groups)
|
* @param array $user The user data (must include user_id, is_admin, groups)
|
||||||
* @return bool True if user can access the ticket
|
* @return bool True if user can access the ticket
|
||||||
*/
|
*/
|
||||||
public function canUserAccessTicket($ticket, $user) {
|
public function canUserAccessTicket(array $ticket, array $user): bool {
|
||||||
// Admins can access all tickets
|
// Admins can access all tickets
|
||||||
if (!empty($user['is_admin'])) {
|
if (!empty($user['is_admin'])) {
|
||||||
return true;
|
return true;
|
||||||
@@ -505,7 +505,7 @@ class TicketModel {
|
|||||||
* @param array $user The current user
|
* @param array $user The current user
|
||||||
* @return array ['sql' => string, 'params' => array, 'types' => string]
|
* @return array ['sql' => string, 'params' => array, 'types' => string]
|
||||||
*/
|
*/
|
||||||
public function getVisibilityFilter($user) {
|
public function getVisibilityFilter(array $user): array {
|
||||||
// Admins see all tickets
|
// Admins see all tickets
|
||||||
if (!empty($user['is_admin'])) {
|
if (!empty($user['is_admin'])) {
|
||||||
return ['sql' => '1=1', 'params' => [], 'types' => ''];
|
return ['sql' => '1=1', 'params' => [], 'types' => ''];
|
||||||
@@ -558,7 +558,7 @@ class TicketModel {
|
|||||||
* @param int $updatedBy User ID
|
* @param int $updatedBy User ID
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function updateVisibility($ticketId, $visibility, $visibilityGroups, $updatedBy) {
|
public function updateVisibility(int $ticketId, string $visibility, ?string $visibilityGroups, int $updatedBy): bool {
|
||||||
$allowedVisibilities = ['public', 'internal', 'confidential'];
|
$allowedVisibilities = ['public', 'internal', 'confidential'];
|
||||||
if (!in_array($visibility, $allowedVisibilities)) {
|
if (!in_array($visibility, $allowedVisibilities)) {
|
||||||
$visibility = 'public';
|
$visibility = 'public';
|
||||||
|
|||||||
@@ -3,18 +3,18 @@
|
|||||||
* UserModel - Handles user authentication and management
|
* UserModel - Handles user authentication and management
|
||||||
*/
|
*/
|
||||||
class UserModel {
|
class UserModel {
|
||||||
private $conn;
|
private mysqli $conn;
|
||||||
private static $userCache = []; // ['key' => ['data' => ..., 'expires' => timestamp]]
|
private static array $userCache = []; // ['key' => ['data' => ..., 'expires' => timestamp]]
|
||||||
private static $cacheTTL = 300; // 5 minutes
|
private static int $cacheTTL = 300; // 5 minutes
|
||||||
|
|
||||||
public function __construct($conn) {
|
public function __construct(mysqli $conn) {
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cached user data if not expired
|
* Get cached user data if not expired
|
||||||
*/
|
*/
|
||||||
private static function getCached($key) {
|
private static function getCached(string $key): ?array {
|
||||||
if (isset(self::$userCache[$key])) {
|
if (isset(self::$userCache[$key])) {
|
||||||
$cached = self::$userCache[$key];
|
$cached = self::$userCache[$key];
|
||||||
if ($cached['expires'] > time()) {
|
if ($cached['expires'] > time()) {
|
||||||
@@ -29,7 +29,7 @@ class UserModel {
|
|||||||
/**
|
/**
|
||||||
* Store user data in cache with expiration
|
* Store user data in cache with expiration
|
||||||
*/
|
*/
|
||||||
private static function setCached($key, $data) {
|
private static function setCached(string $key, array $data): void {
|
||||||
self::$userCache[$key] = [
|
self::$userCache[$key] = [
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
'expires' => time() + self::$cacheTTL
|
'expires' => time() + self::$cacheTTL
|
||||||
@@ -39,7 +39,7 @@ class UserModel {
|
|||||||
/**
|
/**
|
||||||
* Invalidate specific user cache entry
|
* Invalidate specific user cache entry
|
||||||
*/
|
*/
|
||||||
public static function invalidateCache($userId = null, $username = null) {
|
public static function invalidateCache(?int $userId = null, ?string $username = null): void {
|
||||||
if ($userId !== null) {
|
if ($userId !== null) {
|
||||||
unset(self::$userCache["user_id_$userId"]);
|
unset(self::$userCache["user_id_$userId"]);
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@ class UserModel {
|
|||||||
* @param string $groups Comma-separated groups from Remote-Groups header
|
* @param string $groups Comma-separated groups from Remote-Groups header
|
||||||
* @return array User data array
|
* @return array User data array
|
||||||
*/
|
*/
|
||||||
public function syncUserFromAuthelia($username, $displayName = '', $email = '', $groups = '') {
|
public function syncUserFromAuthelia(string $username, string $displayName = '', string $email = '', string $groups = ''): array {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
$cacheKey = "user_$username";
|
$cacheKey = "user_$username";
|
||||||
$cached = self::getCached($cacheKey);
|
$cached = self::getCached($cacheKey);
|
||||||
@@ -122,7 +122,7 @@ class UserModel {
|
|||||||
*
|
*
|
||||||
* @return array|null System user data or null if not found
|
* @return array|null System user data or null if not found
|
||||||
*/
|
*/
|
||||||
public function getSystemUser() {
|
public function getSystemUser(): ?array {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
$cached = self::getCached('system');
|
$cached = self::getCached('system');
|
||||||
if ($cached !== null) {
|
if ($cached !== null) {
|
||||||
@@ -150,7 +150,7 @@ class UserModel {
|
|||||||
* @param int $userId User ID
|
* @param int $userId User ID
|
||||||
* @return array|null User data or null if not found
|
* @return array|null User data or null if not found
|
||||||
*/
|
*/
|
||||||
public function getUserById($userId) {
|
public function getUserById(int $userId): ?array {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
$cacheKey = "user_id_$userId";
|
$cacheKey = "user_id_$userId";
|
||||||
$cached = self::getCached($cacheKey);
|
$cached = self::getCached($cacheKey);
|
||||||
@@ -180,7 +180,7 @@ class UserModel {
|
|||||||
* @param string $username Username
|
* @param string $username Username
|
||||||
* @return array|null User data or null if not found
|
* @return array|null User data or null if not found
|
||||||
*/
|
*/
|
||||||
public function getUserByUsername($username) {
|
public function getUserByUsername(string $username): ?array {
|
||||||
// Check cache first
|
// Check cache first
|
||||||
$cacheKey = "user_$username";
|
$cacheKey = "user_$username";
|
||||||
$cached = self::getCached($cacheKey);
|
$cached = self::getCached($cacheKey);
|
||||||
@@ -210,7 +210,7 @@ class UserModel {
|
|||||||
* @param string $groups Comma-separated group names
|
* @param string $groups Comma-separated group names
|
||||||
* @return bool True if user is in admin group
|
* @return bool True if user is in admin group
|
||||||
*/
|
*/
|
||||||
private function checkAdminStatus($groups) {
|
private function checkAdminStatus(string $groups): bool {
|
||||||
if (empty($groups)) {
|
if (empty($groups)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ class UserModel {
|
|||||||
* @param array $user User data array
|
* @param array $user User data array
|
||||||
* @return bool True if user is admin
|
* @return bool True if user is admin
|
||||||
*/
|
*/
|
||||||
public function isAdmin($user) {
|
public function isAdmin(array $user): bool {
|
||||||
return isset($user['is_admin']) && $user['is_admin'] == 1;
|
return isset($user['is_admin']) && $user['is_admin'] == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ class UserModel {
|
|||||||
* @param array $requiredGroups Array of required group names
|
* @param array $requiredGroups Array of required group names
|
||||||
* @return bool True if user is in at least one required group
|
* @return bool True if user is in at least one required group
|
||||||
*/
|
*/
|
||||||
public function hasGroupAccess($user, $requiredGroups = ['admin', 'employee']) {
|
public function hasGroupAccess(array $user, array $requiredGroups = ['admin', 'employee']): bool {
|
||||||
if (empty($user['groups'])) {
|
if (empty($user['groups'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -253,7 +253,7 @@ class UserModel {
|
|||||||
*
|
*
|
||||||
* @return array Array of user records
|
* @return array Array of user records
|
||||||
*/
|
*/
|
||||||
public function getAllUsers() {
|
public function getAllUsers(): array {
|
||||||
$stmt = $this->conn->prepare("SELECT * FROM users ORDER BY created_at DESC");
|
$stmt = $this->conn->prepare("SELECT * FROM users ORDER BY created_at DESC");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
@@ -273,7 +273,7 @@ class UserModel {
|
|||||||
*
|
*
|
||||||
* @return array Array of unique group names
|
* @return array Array of unique group names
|
||||||
*/
|
*/
|
||||||
public function getAllGroups() {
|
public function getAllGroups(): array {
|
||||||
$stmt = $this->conn->prepare("SELECT DISTINCT groups FROM users WHERE groups IS NOT NULL AND groups != ''");
|
$stmt = $this->conn->prepare("SELECT DISTINCT groups FROM users WHERE groups IS NOT NULL AND groups != ''");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
require_once dirname(__DIR__) . '/helpers/CacheHelper.php';
|
require_once dirname(__DIR__) . '/helpers/CacheHelper.php';
|
||||||
|
|
||||||
class UserPreferencesModel {
|
class UserPreferencesModel {
|
||||||
private $conn;
|
private mysqli $conn;
|
||||||
private static $CACHE_PREFIX = 'user_prefs';
|
private static string $CACHE_PREFIX = 'user_prefs';
|
||||||
private static $CACHE_TTL = 300; // 5 minutes
|
private static int $CACHE_TTL = 300; // 5 minutes
|
||||||
|
|
||||||
public function __construct($conn) {
|
public function __construct(mysqli $conn) {
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ class UserPreferencesModel {
|
|||||||
* @param int $userId User ID
|
* @param int $userId User ID
|
||||||
* @return array Associative array of preference_key => preference_value
|
* @return array Associative array of preference_key => preference_value
|
||||||
*/
|
*/
|
||||||
public function getUserPreferences($userId) {
|
public function getUserPreferences(int $userId): array {
|
||||||
return CacheHelper::remember(self::$CACHE_PREFIX, $userId, function() use ($userId) {
|
return CacheHelper::remember(self::$CACHE_PREFIX, $userId, function() use ($userId) {
|
||||||
$sql = "SELECT preference_key, preference_value
|
$sql = "SELECT preference_key, preference_value
|
||||||
FROM user_preferences
|
FROM user_preferences
|
||||||
@@ -45,7 +45,7 @@ class UserPreferencesModel {
|
|||||||
* @param string $value Preference value
|
* @param string $value Preference value
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function setPreference($userId, $key, $value) {
|
public function setPreference(int $userId, string $key, string $value): bool {
|
||||||
$sql = "INSERT INTO user_preferences (user_id, preference_key, preference_value)
|
$sql = "INSERT INTO user_preferences (user_id, preference_key, preference_value)
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
ON DUPLICATE KEY UPDATE preference_value = VALUES(preference_value)";
|
ON DUPLICATE KEY UPDATE preference_value = VALUES(preference_value)";
|
||||||
@@ -69,7 +69,7 @@ class UserPreferencesModel {
|
|||||||
* @param mixed $default Default value if preference doesn't exist
|
* @param mixed $default Default value if preference doesn't exist
|
||||||
* @return mixed Preference value or default
|
* @return mixed Preference value or default
|
||||||
*/
|
*/
|
||||||
public function getPreference($userId, $key, $default = null) {
|
public function getPreference(int $userId, string $key, $default = null) {
|
||||||
$prefs = $this->getUserPreferences($userId);
|
$prefs = $this->getUserPreferences($userId);
|
||||||
return $prefs[$key] ?? $default;
|
return $prefs[$key] ?? $default;
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ class UserPreferencesModel {
|
|||||||
* @param string $key Preference key
|
* @param string $key Preference key
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function deletePreference($userId, $key) {
|
public function deletePreference(int $userId, string $key): bool {
|
||||||
$sql = "DELETE FROM user_preferences
|
$sql = "DELETE FROM user_preferences
|
||||||
WHERE user_id = ? AND preference_key = ?";
|
WHERE user_id = ? AND preference_key = ?";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
@@ -101,7 +101,7 @@ class UserPreferencesModel {
|
|||||||
* @param int $userId User ID
|
* @param int $userId User ID
|
||||||
* @return bool Success status
|
* @return bool Success status
|
||||||
*/
|
*/
|
||||||
public function deleteAllPreferences($userId) {
|
public function deleteAllPreferences(int $userId): bool {
|
||||||
$sql = "DELETE FROM user_preferences WHERE user_id = ?";
|
$sql = "DELETE FROM user_preferences WHERE user_id = ?";
|
||||||
$stmt = $this->conn->prepare($sql);
|
$stmt = $this->conn->prepare($sql);
|
||||||
$stmt->bind_param("i", $userId);
|
$stmt->bind_param("i", $userId);
|
||||||
@@ -119,7 +119,7 @@ class UserPreferencesModel {
|
|||||||
/**
|
/**
|
||||||
* Clear all user preferences cache
|
* Clear all user preferences cache
|
||||||
*/
|
*/
|
||||||
public static function clearCache() {
|
public static function clearCache(): void {
|
||||||
CacheHelper::delete(self::$CACHE_PREFIX);
|
CacheHelper::delete(self::$CACHE_PREFIX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
require_once dirname(__DIR__) . '/helpers/CacheHelper.php';
|
require_once dirname(__DIR__) . '/helpers/CacheHelper.php';
|
||||||
|
|
||||||
class WorkflowModel {
|
class WorkflowModel {
|
||||||
private $conn;
|
private mysqli $conn;
|
||||||
private static $CACHE_PREFIX = 'workflow';
|
private static string $CACHE_PREFIX = 'workflow';
|
||||||
private static $CACHE_TTL = 600; // 10 minutes
|
private static int $CACHE_TTL = 600; // 10 minutes
|
||||||
|
|
||||||
public function __construct($conn) {
|
public function __construct(mysqli $conn) {
|
||||||
$this->conn = $conn;
|
$this->conn = $conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class WorkflowModel {
|
|||||||
*
|
*
|
||||||
* @return array All active transitions indexed by from_status
|
* @return array All active transitions indexed by from_status
|
||||||
*/
|
*/
|
||||||
private function getAllTransitions() {
|
private function getAllTransitions(): array {
|
||||||
return CacheHelper::remember(self::$CACHE_PREFIX, 'all_transitions', function() {
|
return CacheHelper::remember(self::$CACHE_PREFIX, 'all_transitions', function() {
|
||||||
$sql = "SELECT from_status, to_status, requires_comment, requires_admin
|
$sql = "SELECT from_status, to_status, requires_comment, requires_admin
|
||||||
FROM status_transitions
|
FROM status_transitions
|
||||||
@@ -50,7 +50,7 @@ class WorkflowModel {
|
|||||||
* @param string $currentStatus Current ticket status
|
* @param string $currentStatus Current ticket status
|
||||||
* @return array Array of allowed transitions with requirements
|
* @return array Array of allowed transitions with requirements
|
||||||
*/
|
*/
|
||||||
public function getAllowedTransitions($currentStatus) {
|
public function getAllowedTransitions(string $currentStatus): array {
|
||||||
$allTransitions = $this->getAllTransitions();
|
$allTransitions = $this->getAllTransitions();
|
||||||
|
|
||||||
if (!isset($allTransitions[$currentStatus])) {
|
if (!isset($allTransitions[$currentStatus])) {
|
||||||
@@ -68,7 +68,7 @@ class WorkflowModel {
|
|||||||
* @param bool $isAdmin Whether user is admin
|
* @param bool $isAdmin Whether user is admin
|
||||||
* @return bool True if transition is allowed
|
* @return bool True if transition is allowed
|
||||||
*/
|
*/
|
||||||
public function isTransitionAllowed($fromStatus, $toStatus, $isAdmin = false) {
|
public function isTransitionAllowed(string $fromStatus, string $toStatus, bool $isAdmin = false): bool {
|
||||||
// Allow same status (no change)
|
// Allow same status (no change)
|
||||||
if ($fromStatus === $toStatus) {
|
if ($fromStatus === $toStatus) {
|
||||||
return true;
|
return true;
|
||||||
@@ -94,7 +94,7 @@ class WorkflowModel {
|
|||||||
*
|
*
|
||||||
* @return array Array of unique status values
|
* @return array Array of unique status values
|
||||||
*/
|
*/
|
||||||
public function getAllStatuses() {
|
public function getAllStatuses(): array {
|
||||||
return CacheHelper::remember(self::$CACHE_PREFIX, 'all_statuses', function() {
|
return CacheHelper::remember(self::$CACHE_PREFIX, 'all_statuses', function() {
|
||||||
$sql = "SELECT DISTINCT from_status as status FROM status_transitions
|
$sql = "SELECT DISTINCT from_status as status FROM status_transitions
|
||||||
UNION
|
UNION
|
||||||
@@ -118,7 +118,7 @@ class WorkflowModel {
|
|||||||
* @param string $toStatus Desired status
|
* @param string $toStatus Desired status
|
||||||
* @return array|null Transition requirements or null if not found
|
* @return array|null Transition requirements or null if not found
|
||||||
*/
|
*/
|
||||||
public function getTransitionRequirements($fromStatus, $toStatus) {
|
public function getTransitionRequirements(string $fromStatus, string $toStatus): ?array {
|
||||||
$allTransitions = $this->getAllTransitions();
|
$allTransitions = $this->getAllTransitions();
|
||||||
|
|
||||||
if (!isset($allTransitions[$fromStatus][$toStatus])) {
|
if (!isset($allTransitions[$fromStatus][$toStatus])) {
|
||||||
@@ -135,7 +135,7 @@ class WorkflowModel {
|
|||||||
/**
|
/**
|
||||||
* Clear workflow cache (call when transitions are modified)
|
* Clear workflow cache (call when transitions are modified)
|
||||||
*/
|
*/
|
||||||
public static function clearCache() {
|
public static function clearCache(): void {
|
||||||
CacheHelper::delete(self::$CACHE_PREFIX);
|
CacheHelper::delete(self::$CACHE_PREFIX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user