uploadDir = $uploadDir ?? __DIR__ . '/../public/images/'; $this->baseUrl = $baseUrl ?? '/images/'; // Ensure upload directory exists if (!file_exists($this->uploadDir)) { mkdir($this->uploadDir, 0755, true); } } /** * Process base64 image data and save to file * * @param string $base64Data Base64 encoded image data * @param string $prefix Prefix for filename (e.g., 'poster', 'banner') * @return string|null Relative path to saved image, or null if invalid */ public function saveBase64Image(string $base64Data, string $prefix = 'image'): ?string { error_log("ImageHandler: Starting to process base64 image, length: " . strlen($base64Data)); if (empty($base64Data)) { error_log("ImageHandler: Empty base64 data"); return null; } // Check if it's already a URL (not base64) if (filter_var($base64Data, FILTER_VALIDATE_URL)) { error_log("ImageHandler: Data is already a URL: " . $base64Data); return $base64Data; } // Check if it's already a file path if (strpos($base64Data, '/images/') === 0) { error_log("ImageHandler: Data is already a file path: " . $base64Data); return $base64Data; } // Parse base64 data if (preg_match('/^data:image\/(\w+);base64,(.+)$/', $base64Data, $matches)) { $extension = $matches[1]; $base64String = $matches[2]; error_log("ImageHandler: Data URI format detected, extension: " . $extension); } elseif (preg_match('/^\/9j\//', $base64Data)) { // Raw base64 without data URI prefix (JPEG) $extension = 'jpg'; $base64String = $base64Data; error_log("ImageHandler: Raw JPEG base64 detected"); } else { // Try to detect from the base64 string itself $extension = $this->detectImageFormat($base64Data); if (!$extension) { error_log("ImageHandler: Could not detect image format, defaulting to jpg"); $extension = 'jpg'; $base64String = $base64Data; } else { $base64String = $base64Data; error_log("ImageHandler: Detected format: " . $extension); } } // Decode base64 $imageData = base64_decode($base64String); if ($imageData === false) { error_log("ImageHandler: Base64 decode failed"); return null; } error_log("ImageHandler: Decoded image data size: " . strlen($imageData) . " bytes"); // Skip GD validation - just check if data looks reasonable if (strlen($imageData) < 100) { error_log("ImageHandler: Image data too small, likely invalid"); return null; } // Generate unique filename $filename = $this->generateUniqueFilename($prefix, $extension); $filepath = $this->uploadDir . $filename; error_log("ImageHandler: Attempting to save to: " . $filepath); // Ensure directory exists and is writable (handle subdirectories) $directory = dirname($filepath); if (!file_exists($directory)) { error_log("ImageHandler: Creating directory: " . $directory); if (!mkdir($directory, 0755, true)) { error_log("ImageHandler: Failed to create directory"); return null; } } if (!is_writable($directory)) { error_log("ImageHandler: Directory not writable: " . $directory); error_log("ImageHandler: Attempting to chmod directory"); chmod($directory, 0755); } // Save file $bytesWritten = file_put_contents($filepath, $imageData); if ($bytesWritten === false) { error_log("ImageHandler: Failed to save file to: " . $filepath); error_log("ImageHandler: Upload directory exists: " . (file_exists($this->uploadDir) ? 'yes' : 'no')); error_log("ImageHandler: Upload directory writable: " . (is_writable($this->uploadDir) ? 'yes' : 'no')); return null; } error_log("ImageHandler: Successfully saved " . $bytesWritten . " bytes to: " . $filepath); // Return relative path return $this->baseUrl . $filename; } /** * Detect image format from base64 string */ private function detectImageFormat(string $base64String): ?string { // Decode first few bytes to check magic numbers $data = base64_decode(substr($base64String, 0, 100)); if (substr($data, 0, 3) === "\xFF\xD8\xFF") { return 'jpg'; } elseif (substr($data, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { return 'png'; } elseif (substr($data, 0, 6) === "GIF87a" || substr($data, 0, 6) === "GIF89a") { return 'gif'; } elseif (substr($data, 0, 4) === "RIFF" && substr($data, 8, 4) === "WEBP") { return 'webp'; } // Default to jpg for game posters return 'jpg'; } /** * Validate that data is a valid image */ private function isValidImage(string $data): bool { try { $image = imagecreatefromstring($data); if ($image !== false) { imagedestroy($image); return true; } } catch (Exception $e) { return false; } return false; } /** * Generate unique filename */ private function generateUniqueFilename(string $prefix, string $extension): string { return $prefix . '_' . uniqid() . '_' . time() . '.' . $extension; } /** * Delete an image file */ public function deleteImage(?string $imagePath): bool { if (empty($imagePath)) { return false; } // Convert URL to filesystem path if (strpos($imagePath, $this->baseUrl) === 0) { $filename = substr($imagePath, strlen($this->baseUrl)); $filepath = $this->uploadDir . $filename; if (file_exists($filepath)) { return unlink($filepath); } } return false; } }