diff --git a/helpers/CacheHelper.php b/helpers/CacheHelper.php index a204bcc..0c13214 100644 --- a/helpers/CacheHelper.php +++ b/helpers/CacheHelper.php @@ -6,15 +6,15 @@ * such as workflow rules, user preferences, and configuration data. */ class CacheHelper { - private static $cacheDir = null; - private static $memoryCache = []; + private static ?string $cacheDir = null; + private static array $memoryCache = []; /** * Get the cache directory path * * @return string Cache directory path */ - private static function getCacheDir() { + private static function getCacheDir(): string { if (self::$cacheDir === null) { self::$cacheDir = sys_get_temp_dir() . '/tinker_tickets_cache'; if (!is_dir(self::$cacheDir)) { @@ -31,7 +31,7 @@ class CacheHelper { * @param mixed $identifier Unique identifier * @return string Cache key */ - private static function makeKey($prefix, $identifier = null) { + private static function makeKey(string $prefix, $identifier = null): string { $key = $prefix; if ($identifier !== null) { $key .= '_' . md5(serialize($identifier)); @@ -47,7 +47,7 @@ class CacheHelper { * @param int $ttl Time-to-live in seconds (default 300 = 5 minutes) * @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); // Check memory cache first (fastest) @@ -88,7 +88,7 @@ class CacheHelper { * @param mixed $data Data to cache * @return bool Success */ - public static function set($prefix, $identifier, $data) { + public static function set(string $prefix, $identifier, $data): bool { $key = self::makeKey($prefix, $identifier); $cached = [ 'time' => time(), @@ -110,7 +110,7 @@ class CacheHelper { * @param mixed $identifier Unique identifier (null to delete all with prefix) * @return bool Success */ - public static function delete($prefix, $identifier = null) { + public static function delete(string $prefix, $identifier = null): bool { if ($identifier !== null) { $key = self::makeKey($prefix, $identifier); unset(self::$memoryCache[$key]); @@ -140,7 +140,7 @@ class CacheHelper { * * @return bool Success */ - public static function clearAll() { + public static function clearAll(): bool { self::$memoryCache = []; $files = glob(self::getCacheDir() . '/*.json'); @@ -160,7 +160,7 @@ class CacheHelper { * @param int $ttl Time-to-live in seconds * @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); if ($data === null) { @@ -178,7 +178,7 @@ class CacheHelper { * * @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'); $now = time(); diff --git a/helpers/Database.php b/helpers/Database.php index 484b8f9..7e749c4 100644 --- a/helpers/Database.php +++ b/helpers/Database.php @@ -6,7 +6,7 @@ * Provides a singleton connection for the request lifecycle. */ class Database { - private static $connection = null; + private static ?mysqli $connection = null; /** * Get database connection (singleton pattern) @@ -14,7 +14,7 @@ class Database { * @return mysqli Database connection * @throws Exception If connection fails */ - public static function getConnection() { + public static function getConnection(): mysqli { if (self::$connection === null) { self::$connection = self::createConnection(); } @@ -33,7 +33,7 @@ class Database { * @return mysqli Database connection * @throws Exception If connection fails */ - private static function createConnection() { + private static function createConnection(): mysqli { // Ensure config is loaded if (!isset($GLOBALS['config'])) { require_once dirname(__DIR__) . '/config/config.php'; @@ -59,7 +59,7 @@ class Database { /** * Close the database connection */ - public static function close() { + public static function close(): void { if (self::$connection !== null) { self::$connection->close(); self::$connection = null; @@ -71,7 +71,7 @@ class Database { * * @return bool Success */ - public static function beginTransaction() { + public static function beginTransaction(): bool { return self::getConnection()->begin_transaction(); } @@ -80,7 +80,7 @@ class Database { * * @return bool Success */ - public static function commit() { + public static function commit(): bool { return self::getConnection()->commit(); } @@ -89,7 +89,7 @@ class Database { * * @return bool Success */ - public static function rollback() { + public static function rollback(): bool { return self::getConnection()->rollback(); } @@ -101,7 +101,7 @@ class Database { * @param array $params Parameters to bind * @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(); if (empty($types) || empty($params)) { @@ -130,7 +130,7 @@ class Database { * @param array $params Parameters to bind * @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(); $stmt = $conn->prepare($sql); @@ -158,7 +158,7 @@ class Database { * * @return int Last insert ID */ - public static function lastInsertId() { + public static function lastInsertId(): int { return self::getConnection()->insert_id; } @@ -168,7 +168,7 @@ class Database { * @param string $string String to escape * @return string Escaped string */ - public static function escape($string) { + public static function escape(string $string): string { return self::getConnection()->real_escape_string($string); } } diff --git a/helpers/ErrorHandler.php b/helpers/ErrorHandler.php index 82de9e4..0c1fe52 100644 --- a/helpers/ErrorHandler.php +++ b/helpers/ErrorHandler.php @@ -6,15 +6,15 @@ * across the application. */ class ErrorHandler { - private static $logFile = null; - private static $initialized = false; + private static ?string $logFile = null; + private static bool $initialized = false; /** * Initialize error handling * * @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) { return; } @@ -45,7 +45,7 @@ class ErrorHandler { * @param int $errline Line number * @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 if (!(error_reporting() & $errno)) { return false; @@ -69,7 +69,7 @@ class ErrorHandler { * * @param Throwable $exception */ - public static function handleException($exception) { + public static function handleException(Throwable $exception): void { $message = sprintf( "Uncaught %s: %s in %s on line %d\nStack trace:\n%s", get_class($exception), @@ -94,7 +94,7 @@ class ErrorHandler { /** * Handle fatal errors on shutdown */ - public static function handleShutdown() { + public static function handleShutdown(): void { $error = error_get_last(); 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 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'); $levelName = self::getErrorTypeName($level); @@ -140,7 +140,7 @@ class ErrorHandler { * @param int $httpCode HTTP status code * @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); if (!headers_sent()) { @@ -172,7 +172,7 @@ class ErrorHandler { * @param array $errors Array of validation errors * @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); if (!headers_sent()) { @@ -192,7 +192,7 @@ class ErrorHandler { * * @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); } @@ -201,7 +201,7 @@ class ErrorHandler { * * @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); } @@ -210,7 +210,7 @@ class ErrorHandler { * * @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); } @@ -220,7 +220,7 @@ class ErrorHandler { * @param int $errno Error number * @return string Error type name */ - private static function getErrorTypeName($errno) { + private static function getErrorTypeName(int $errno): string { $types = [ E_ERROR => 'ERROR', E_WARNING => 'WARNING', @@ -248,7 +248,7 @@ class ErrorHandler { * @param int $lines Number of lines to return * @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)) { return []; } diff --git a/middleware/CsrfMiddleware.php b/middleware/CsrfMiddleware.php index 866f5ed..c089257 100644 --- a/middleware/CsrfMiddleware.php +++ b/middleware/CsrfMiddleware.php @@ -4,14 +4,14 @@ * Generates and validates CSRF tokens for all state-changing operations */ class CsrfMiddleware { - private static $tokenName = 'csrf_token'; - private static $tokenTime = 'csrf_token_time'; - private static $tokenLifetime = 3600; // 1 hour + private static string $tokenName = 'csrf_token'; + private static string $tokenTime = 'csrf_token_time'; + private static int $tokenLifetime = 3600; // 1 hour /** * Generate a new CSRF token */ - public static function generateToken() { + public static function generateToken(): string { $_SESSION[self::$tokenName] = bin2hex(random_bytes(32)); $_SESSION[self::$tokenTime] = time(); return $_SESSION[self::$tokenName]; @@ -20,7 +20,7 @@ class CsrfMiddleware { /** * Get current CSRF token, regenerate if expired */ - public static function getToken() { + public static function getToken(): string { if (!isset($_SESSION[self::$tokenName]) || self::isTokenExpired()) { return self::generateToken(); } @@ -30,7 +30,7 @@ class CsrfMiddleware { /** * Validate CSRF token (constant-time comparison) */ - public static function validateToken($token) { + public static function validateToken(string $token): bool { if (!isset($_SESSION[self::$tokenName])) { return false; } @@ -47,9 +47,8 @@ class CsrfMiddleware { /** * Check if token is expired */ - private static function isTokenExpired() { + private static function isTokenExpired(): bool { return !isset($_SESSION[self::$tokenTime]) || (time() - $_SESSION[self::$tokenTime]) > self::$tokenLifetime; } } -?> diff --git a/middleware/RateLimitMiddleware.php b/middleware/RateLimitMiddleware.php index b90850d..a896838 100644 --- a/middleware/RateLimitMiddleware.php +++ b/middleware/RateLimitMiddleware.php @@ -7,21 +7,21 @@ */ class RateLimitMiddleware { // Default limits - const DEFAULT_LIMIT = 100; // requests per window (session) - const API_LIMIT = 60; // API requests per window (session) - const IP_LIMIT = 300; // IP-based requests per window (more generous) - const IP_API_LIMIT = 120; // IP-based API requests per window - const WINDOW_SECONDS = 60; // 1 minute window + public const DEFAULT_LIMIT = 100; // requests per window (session) + public const API_LIMIT = 60; // API requests per window (session) + public const IP_LIMIT = 300; // IP-based requests per window (more generous) + public const IP_API_LIMIT = 120; // IP-based API requests per window + public const WINDOW_SECONDS = 60; // 1 minute window // Directory for IP rate limit storage - private static $rateLimitDir = null; + private static ?string $rateLimitDir = null; /** * Get the 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) { self::$rateLimitDir = sys_get_temp_dir() . '/tinker_tickets_ratelimit'; if (!is_dir(self::$rateLimitDir)) { @@ -36,7 +36,7 @@ class RateLimitMiddleware { * * @return string Client IP address */ - private static function getClientIp() { + private static function getClientIp(): string { // Check for forwarded IP (behind proxy/load balancer) $headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP']; foreach ($headers as $header) { @@ -58,7 +58,7 @@ class RateLimitMiddleware { * @param string $type 'default' or 'api' * @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(); $limit = $type === 'api' ? self::IP_API_LIMIT : self::IP_LIMIT; $now = time(); @@ -97,7 +97,7 @@ class RateLimitMiddleware { /** * Clean up old rate limit files (call periodically) */ - public static function cleanupOldFiles() { + public static function cleanupOldFiles(): void { $dir = self::getRateLimitDir(); $files = glob($dir . '/*.json'); $now = time(); @@ -116,7 +116,7 @@ class RateLimitMiddleware { * @param string $type 'default' or 'api' * @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) if (!self::checkIpRateLimit($type)) { return false; @@ -164,7 +164,7 @@ class RateLimitMiddleware { * * @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) if (mt_rand(1, 100) === 1) { self::cleanupOldFiles(); @@ -189,7 +189,7 @@ class RateLimitMiddleware { * @param string $type 'default' or 'api' * @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) { session_start(); } @@ -229,7 +229,7 @@ class RateLimitMiddleware { * * @param string $type 'default' or 'api' */ - public static function addHeaders($type = 'default') { + public static function addHeaders(string $type = 'default'): void { $status = self::getStatus($type); header('X-RateLimit-Limit: ' . $status['limit']); header('X-RateLimit-Remaining: ' . $status['remaining']); diff --git a/middleware/SecurityHeadersMiddleware.php b/middleware/SecurityHeadersMiddleware.php index 90bd8e8..5be8c20 100644 --- a/middleware/SecurityHeadersMiddleware.php +++ b/middleware/SecurityHeadersMiddleware.php @@ -5,14 +5,14 @@ * Applies security-related HTTP headers to all responses. */ class SecurityHeadersMiddleware { - private static $nonce = null; + private static ?string $nonce = null; /** * Generate or retrieve the CSP nonce for this request * * @return string The nonce value */ - public static function getNonce() { + public static function getNonce(): string { if (self::$nonce === null) { self::$nonce = base64_encode(random_bytes(16)); } @@ -22,7 +22,7 @@ class SecurityHeadersMiddleware { /** * Apply security headers to the response */ - public static function apply() { + public static function apply(): void { $nonce = self::getNonce(); // Content Security Policy - restricts where resources can be loaded from diff --git a/models/TemplateModel.php b/models/TemplateModel.php index 3e8fda9..3ecb567 100644 --- a/models/TemplateModel.php +++ b/models/TemplateModel.php @@ -3,9 +3,9 @@ * TemplateModel - Handles ticket template operations */ class TemplateModel { - private $conn; + private mysqli $conn; - public function __construct($conn) { + public function __construct(mysqli $conn) { $this->conn = $conn; } @@ -14,7 +14,7 @@ class TemplateModel { * * @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"; $result = $this->conn->query($sql); @@ -31,7 +31,7 @@ class TemplateModel { * @param int $templateId Template ID * @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"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $templateId); @@ -50,7 +50,7 @@ class TemplateModel { * @param int $createdBy User ID creating the template * @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, category, type, default_priority, created_by) VALUES (?, ?, ?, ?, ?, ?, ?)"; @@ -77,7 +77,7 @@ class TemplateModel { * @param array $data Template data to update * @return bool Success status */ - public function updateTemplate($templateId, $data) { + public function updateTemplate(int $templateId, array $data): bool { $sql = "UPDATE ticket_templates SET template_name = ?, title_template = ?, @@ -108,7 +108,7 @@ class TemplateModel { * @param int $templateId Template ID * @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 = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $templateId); diff --git a/models/TicketModel.php b/models/TicketModel.php index c9e1776..b7a382e 100644 --- a/models/TicketModel.php +++ b/models/TicketModel.php @@ -1,12 +1,12 @@ conn = $conn; } - - public function getTicketById($id) { + + public function getTicketById(int $id): ?array { $sql = "SELECT t.*, u_created.username as creator_username, u_created.display_name as creator_display_name, @@ -31,7 +31,7 @@ class TicketModel { 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"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $ticketId); @@ -46,7 +46,7 @@ class TicketModel { 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 $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 title = ?, priority = ?, @@ -257,7 +257,7 @@ class TicketModel { 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) // Loop until we find an ID that doesn't exist to prevent collisions $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) VALUES (?, ?, ?, ?)"; @@ -387,7 +387,7 @@ class TicketModel { * @param int $assignedBy User ID performing the assignment * @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 = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("iii", $userId, $assignedBy, $ticketId); @@ -403,7 +403,7 @@ class TicketModel { * @param int $updatedBy User ID performing the unassignment * @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 = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("ii", $updatedBy, $ticketId); @@ -419,7 +419,7 @@ class TicketModel { * @param array $ticketIds Array of ticket IDs * @return array Associative array keyed by ticket_id */ - public function getTicketsByIds($ticketIds) { + public function getTicketsByIds(array $ticketIds): array { if (empty($ticketIds)) { return []; } @@ -465,7 +465,7 @@ class TicketModel { * @param array $user The user data (must include user_id, is_admin, groups) * @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 if (!empty($user['is_admin'])) { return true; @@ -505,7 +505,7 @@ class TicketModel { * @param array $user The current user * @return array ['sql' => string, 'params' => array, 'types' => string] */ - public function getVisibilityFilter($user) { + public function getVisibilityFilter(array $user): array { // Admins see all tickets if (!empty($user['is_admin'])) { return ['sql' => '1=1', 'params' => [], 'types' => '']; @@ -558,7 +558,7 @@ class TicketModel { * @param int $updatedBy User ID * @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']; if (!in_array($visibility, $allowedVisibilities)) { $visibility = 'public'; diff --git a/models/UserModel.php b/models/UserModel.php index 5bce66d..af3be79 100644 --- a/models/UserModel.php +++ b/models/UserModel.php @@ -3,18 +3,18 @@ * UserModel - Handles user authentication and management */ class UserModel { - private $conn; - private static $userCache = []; // ['key' => ['data' => ..., 'expires' => timestamp]] - private static $cacheTTL = 300; // 5 minutes + private mysqli $conn; + private static array $userCache = []; // ['key' => ['data' => ..., 'expires' => timestamp]] + private static int $cacheTTL = 300; // 5 minutes - public function __construct($conn) { + public function __construct(mysqli $conn) { $this->conn = $conn; } /** * Get cached user data if not expired */ - private static function getCached($key) { + private static function getCached(string $key): ?array { if (isset(self::$userCache[$key])) { $cached = self::$userCache[$key]; if ($cached['expires'] > time()) { @@ -29,7 +29,7 @@ class UserModel { /** * 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] = [ 'data' => $data, 'expires' => time() + self::$cacheTTL @@ -39,7 +39,7 @@ class UserModel { /** * 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) { unset(self::$userCache["user_id_$userId"]); } @@ -57,7 +57,7 @@ class UserModel { * @param string $groups Comma-separated groups from Remote-Groups header * @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 $cacheKey = "user_$username"; $cached = self::getCached($cacheKey); @@ -122,7 +122,7 @@ class UserModel { * * @return array|null System user data or null if not found */ - public function getSystemUser() { + public function getSystemUser(): ?array { // Check cache first $cached = self::getCached('system'); if ($cached !== null) { @@ -150,7 +150,7 @@ class UserModel { * @param int $userId User ID * @return array|null User data or null if not found */ - public function getUserById($userId) { + public function getUserById(int $userId): ?array { // Check cache first $cacheKey = "user_id_$userId"; $cached = self::getCached($cacheKey); @@ -180,7 +180,7 @@ class UserModel { * @param string $username Username * @return array|null User data or null if not found */ - public function getUserByUsername($username) { + public function getUserByUsername(string $username): ?array { // Check cache first $cacheKey = "user_$username"; $cached = self::getCached($cacheKey); @@ -210,7 +210,7 @@ class UserModel { * @param string $groups Comma-separated group names * @return bool True if user is in admin group */ - private function checkAdminStatus($groups) { + private function checkAdminStatus(string $groups): bool { if (empty($groups)) { return false; } @@ -226,7 +226,7 @@ class UserModel { * @param array $user User data array * @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; } @@ -237,7 +237,7 @@ class UserModel { * @param array $requiredGroups Array of required group names * @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'])) { return false; } @@ -253,7 +253,7 @@ class UserModel { * * @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->execute(); $result = $stmt->get_result(); @@ -273,7 +273,7 @@ class UserModel { * * @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->execute(); $result = $stmt->get_result(); diff --git a/models/UserPreferencesModel.php b/models/UserPreferencesModel.php index cfa5c92..20efdfe 100644 --- a/models/UserPreferencesModel.php +++ b/models/UserPreferencesModel.php @@ -6,11 +6,11 @@ require_once dirname(__DIR__) . '/helpers/CacheHelper.php'; class UserPreferencesModel { - private $conn; - private static $CACHE_PREFIX = 'user_prefs'; - private static $CACHE_TTL = 300; // 5 minutes + private mysqli $conn; + private static string $CACHE_PREFIX = 'user_prefs'; + private static int $CACHE_TTL = 300; // 5 minutes - public function __construct($conn) { + public function __construct(mysqli $conn) { $this->conn = $conn; } @@ -19,7 +19,7 @@ class UserPreferencesModel { * @param int $userId User ID * @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) { $sql = "SELECT preference_key, preference_value FROM user_preferences @@ -45,7 +45,7 @@ class UserPreferencesModel { * @param string $value Preference value * @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) VALUES (?, ?, ?) 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 * @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); return $prefs[$key] ?? $default; } @@ -80,7 +80,7 @@ class UserPreferencesModel { * @param string $key Preference key * @return bool Success status */ - public function deletePreference($userId, $key) { + public function deletePreference(int $userId, string $key): bool { $sql = "DELETE FROM user_preferences WHERE user_id = ? AND preference_key = ?"; $stmt = $this->conn->prepare($sql); @@ -101,7 +101,7 @@ class UserPreferencesModel { * @param int $userId User ID * @return bool Success status */ - public function deleteAllPreferences($userId) { + public function deleteAllPreferences(int $userId): bool { $sql = "DELETE FROM user_preferences WHERE user_id = ?"; $stmt = $this->conn->prepare($sql); $stmt->bind_param("i", $userId); @@ -119,7 +119,7 @@ class UserPreferencesModel { /** * Clear all user preferences cache */ - public static function clearCache() { + public static function clearCache(): void { CacheHelper::delete(self::$CACHE_PREFIX); } } diff --git a/models/WorkflowModel.php b/models/WorkflowModel.php index c4a0308..d1ba408 100644 --- a/models/WorkflowModel.php +++ b/models/WorkflowModel.php @@ -7,11 +7,11 @@ require_once dirname(__DIR__) . '/helpers/CacheHelper.php'; class WorkflowModel { - private $conn; - private static $CACHE_PREFIX = 'workflow'; - private static $CACHE_TTL = 600; // 10 minutes + private mysqli $conn; + private static string $CACHE_PREFIX = 'workflow'; + private static int $CACHE_TTL = 600; // 10 minutes - public function __construct($conn) { + public function __construct(mysqli $conn) { $this->conn = $conn; } @@ -20,7 +20,7 @@ class WorkflowModel { * * @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() { $sql = "SELECT from_status, to_status, requires_comment, requires_admin FROM status_transitions @@ -50,7 +50,7 @@ class WorkflowModel { * @param string $currentStatus Current ticket status * @return array Array of allowed transitions with requirements */ - public function getAllowedTransitions($currentStatus) { + public function getAllowedTransitions(string $currentStatus): array { $allTransitions = $this->getAllTransitions(); if (!isset($allTransitions[$currentStatus])) { @@ -68,7 +68,7 @@ class WorkflowModel { * @param bool $isAdmin Whether user is admin * @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) if ($fromStatus === $toStatus) { return true; @@ -94,7 +94,7 @@ class WorkflowModel { * * @return array Array of unique status values */ - public function getAllStatuses() { + public function getAllStatuses(): array { return CacheHelper::remember(self::$CACHE_PREFIX, 'all_statuses', function() { $sql = "SELECT DISTINCT from_status as status FROM status_transitions UNION @@ -118,7 +118,7 @@ class WorkflowModel { * @param string $toStatus Desired status * @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(); if (!isset($allTransitions[$fromStatus][$toStatus])) { @@ -135,7 +135,7 @@ class WorkflowModel { /** * Clear workflow cache (call when transitions are modified) */ - public static function clearCache() { + public static function clearCache(): void { CacheHelper::delete(self::$CACHE_PREFIX); } }