'MediaCollector/1.0' ]; if ($apiKey) { $headers['ApiKey'] = $apiKey; } $this->httpClient = new Client([ 'timeout' => 30, 'headers' => $headers, 'verify' => false // Disable SSL verification for problematic servers ]); // Convert relative path to absolute path if (strpos($basePath, '/') !== 0) { $this->basePath = __DIR__ . '/../' . $basePath; } else { $this->basePath = $basePath; } } /** * Download an image from URL and save it locally */ public function downloadImage(string $url, string $filename, string $subfolder = ''): ?string { if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) { error_log("Invalid URL provided: {$url}"); return null; } $maxRetries = 3; $retryDelay = 2; // seconds for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { error_log("Downloading image (attempt {$attempt}/{$maxRetries}): {$url}"); $response = $this->httpClient->get($url, [ 'timeout' => 60, // Longer timeout for large images 'connect_timeout' => 30, 'headers' => [ 'User-Agent' => 'MediaCollector/1.0', 'Referer' => parse_url($url, PHP_URL_HOST) ] ]); if ($response->getStatusCode() !== 200) { error_log("HTTP {$response->getStatusCode()} for {$url}"); if ($attempt === $maxRetries) { return null; } sleep($retryDelay); continue; } $imageData = $response->getBody()->getContents(); if (empty($imageData)) { error_log("Empty response for {$url}"); if ($attempt === $maxRetries) { return null; } sleep($retryDelay); continue; } // Validate image data if (!$this->isValidImage($imageData)) { error_log("Invalid image data for {$url}"); if ($attempt === $maxRetries) { return null; } sleep($retryDelay); continue; } return $this->saveImage($imageData, $filename, $subfolder); } catch (Exception $e) { error_log("Error downloading {$url} (attempt {$attempt}): " . $e->getMessage()); if ($attempt === $maxRetries) { error_log("Failed to download {$url} after {$maxRetries} attempts"); return null; } sleep($retryDelay); } } return null; } private function isValidImage(string $data): bool { // Check for common image signatures $imageTypes = [ 'image/jpeg' => "\xFF\xD8\xFF", 'image/png' => "\x89PNG", 'image/gif' => "GIF", 'image/webp' => "RIFF" ]; foreach ($imageTypes as $type => $signature) { if (strpos($data, $signature) === 0) { return true; } } return false; // Not a valid image type } public function saveImage(string $imageData, string $filename, string $subfolder = ''): ?string { try { // Create subfolder if it doesn't exist $fullPath = $this->basePath; if (!empty($subfolder)) { $fullPath .= '/' . trim($subfolder, '/'); } if (!is_dir($fullPath)) { mkdir($fullPath, 0755, true); } $filePath = $fullPath . '/' . $filename; // Write image data to file $bytesWritten = file_put_contents($filePath, $imageData); if ($bytesWritten === false || $bytesWritten === 0) { error_log("Failed to write image data to {$filePath}"); return null; } return $filePath; } catch (Exception $e) { error_log("Error saving image {$filename}: " . $e->getMessage()); return null; } } public function generateFilename(string $url, string $prefix = ''): string { $extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION); if (empty($extension)) { $extension = 'jpg'; // Default fallback } $hash = substr(md5($url . time()), 0, 8); return ($prefix ? $prefix . '_' : '') . $hash . '.' . $extension; } /** * Get the public URL for a local image */ public function getPublicUrl(string $localPath): ?string { if (empty($localPath) || !file_exists($localPath)) { return null; } // Remove the absolute basePath prefix to get the relative path $relativePath = str_replace($this->basePath . '/', '', $localPath); return '/images/' . $relativePath; } }