apiToken = Configuration::get("ECOMZONE_API_TOKEN"); $this->apiUrl = Configuration::get("ECOMZONE_API_URL"); if (empty($this->apiUrl)) { $this->apiUrl = 'https://dropship.ecomzone.eu/api'; // Default API URL Configuration::updateValue("ECOMZONE_API_URL", $this->apiUrl); } EcomZoneLogger::log("API Client initialized", "INFO", [ "url" => $this->apiUrl, ]); } public function validateApiCredentials() { if (empty($this->apiToken)) { throw new EcomZoneException( "API token not configured", EcomZoneException::API_ERROR ); } return true; } public function getCatalog($page = 1, $perPage = 100) { $this->validateApiCredentials(); $params = [ "page" => $page, "per_page" => $perPage, ]; return $this->makeRequest("catalog", "GET", $params); } public function getProduct($sku) { $this->validateApiCredentials(); return $this->makeRequest("product/" . urlencode($sku), "GET"); } private function makeRequest($endpoint, $method = "GET", $params = [], $data = null) { // Validate API credentials first $this->validateApiCredentials(); $retryCount = 0; $lastError = null; $url = rtrim($this->apiUrl, '/') . '/' . ltrim($endpoint, '/'); // Add query parameters to URL if method is GET if ($method === "GET" && !empty($params) && is_array($params)) { $url .= (strpos($url, "?") === false ? "?" : "&") . http_build_query($params); } do { try { EcomZoneLogger::log("Making API request", "INFO", [ "method" => $method, "url" => $url, "retry" => $retryCount, "params" => $params ]); $curl = curl_init(); $options = [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . $this->apiToken, "Accept: application/json", "Content-Type: application/json", "User-Agent: PrestaShop-EcomZone/1.0", "Connection: close" // Avoid keep-alive issues ], // SSL Options - disable verification for development CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0, // Verbose debugging CURLOPT_VERBOSE => true, ]; // Create a temporary file for curl verbose output $verbose = fopen('php://temp', 'w+'); curl_setopt($curl, CURLOPT_STDERR, $verbose); // Add POST data if provided if ($data !== null) { $jsonData = json_encode($data); curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonData); EcomZoneLogger::log("Request payload", "DEBUG", [ 'data' => $jsonData ]); } // For POST requests with params, add them to the body else if ($method !== "GET" && !empty($params)) { $jsonData = json_encode($params); curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonData); EcomZoneLogger::log("Request params as payload", "DEBUG", [ 'data' => $jsonData ]); } curl_setopt_array($curl, $options); // Execute the request $response = curl_exec($curl); $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); $error = curl_error($curl); $errorNo = curl_errno($curl); // Get verbose information rewind($verbose); $verboseLog = stream_get_contents($verbose); fclose($verbose); // Log detailed response information EcomZoneLogger::log("API Response Details", "DEBUG", [ 'http_code' => $httpCode, 'content_type' => $contentType, 'response_length' => strlen($response), 'curl_error_code' => $errorNo, 'curl_error' => $error, 'verbose_log' => $verboseLog, 'raw_response' => strlen($response) > 1000 ? substr($response, 0, 1000) . '... [truncated]' : $response ]); curl_close($curl); if ($errorNo > 0) { throw new Exception("cURL Error ({$errorNo}): " . $error); } // Handle HTTP errors if ($httpCode >= 400) { $errorMessage = "HTTP Error: " . $httpCode; // Try to extract error details from response if (!empty($response)) { try { $errorData = json_decode($response, true); if (json_last_error() === JSON_ERROR_NONE && !empty($errorData['error'])) { $errorMessage .= " - " . $errorData['error']; } else { $errorMessage .= " - Response: " . substr($response, 0, 200); } } catch (Exception $e) { $errorMessage .= " - Raw response: " . substr($response, 0, 200); } } throw new Exception($errorMessage); } // Handle empty responses if (empty($response)) { throw new Exception("Empty response received from API"); } // Decode JSON response $decodedResponse = json_decode($response, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new Exception( "Invalid JSON response: " . json_last_error_msg() . "\nRaw response: " . substr($response, 0, 500) ); } EcomZoneLogger::log("API request successful", "INFO", [ "http_code" => $httpCode, "content_type" => $contentType ]); return $decodedResponse; } catch (Exception $e) { $lastError = $e; $retryCount++; EcomZoneLogger::log("API request failed", "ERROR", [ "error" => $e->getMessage(), "retry" => $retryCount, "url" => $url, "trace" => $e->getTraceAsString() ]); if ($retryCount < $this->maxRetries) { $sleepTime = pow(2, $retryCount); EcomZoneLogger::log("Retrying request", "INFO", [ "retry_count" => $retryCount, "sleep_time" => $sleepTime, "url" => $url ]); sleep($sleepTime); continue; } throw new EcomZoneException( "API request failed after {$this->maxRetries} retries: " . $e->getMessage(), EcomZoneException::API_ERROR, $e ); } } while ($retryCount < $this->maxRetries); // This should never be reached, but just in case throw new EcomZoneException( "Max retries reached: " . $lastError->getMessage(), EcomZoneException::API_ERROR, $lastError ); } /** * Download an image from the EcomZone API with proper authentication * * @param string $imageUrl The full URL of the image to download * @return array Array containing image data and metadata * @throws EcomZoneException If the download fails */ public function downloadImage(string $imageUrl): array { try { EcomZoneLogger::log("Downloading image", "DEBUG", [ 'url' => $imageUrl ]); // Initialize cURL $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $imageUrl, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => 0, CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', CURLOPT_HTTPHEADER => [ 'Accept: image/webp,image/apng,image/*,*/*;q=0.8', 'Accept-Encoding: gzip, deflate, br', 'Authorization: Bearer ' . $this->getApiToken() ] ]); $imageData = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); $error = curl_error($ch); curl_close($ch); // Check if the response is HTML instead of an image (authentication error) $isHtml = stripos($contentType, 'text/html') !== false || (strlen($imageData) > 15 && stripos($imageData, '') !== false); if ($isHtml) { throw new EcomZoneException( "Authentication error: Received HTML instead of image data. Check API token.", EcomZoneException::API_ERROR ); } if ($httpCode !== 200 || empty($imageData)) { throw new EcomZoneException( "Failed to download image (HTTP $httpCode): $error", EcomZoneException::API_ERROR ); } EcomZoneLogger::log("Image download complete", "DEBUG", [ 'url' => $imageUrl, 'content_type' => $contentType, 'size' => strlen($imageData) ]); return [ 'data' => $imageData, 'content_type' => $contentType, 'http_code' => $httpCode ]; } catch (Exception $e) { EcomZoneLogger::log("Image download failed", "ERROR", [ 'url' => $imageUrl, 'error' => $e->getMessage() ]); throw new EcomZoneException( "Image download failed: " . $e->getMessage(), EcomZoneException::API_ERROR, $e ); } } /** * Get the API token, either from the class property or from the configuration * * @return string The API token */ protected function getApiToken() { if (empty($this->apiToken)) { $this->apiToken = Configuration::get("ECOMZONE_API_TOKEN"); } if (empty($this->apiToken)) { throw new EcomZoneException( "API token not configured", EcomZoneException::API_ERROR ); } return $this->apiToken; } }