backup
This commit is contained in:
parent
c8bcfe6bd6
commit
9c277e6db8
8
docs.md
Normal file
8
docs.md
Normal file
@ -0,0 +1,8 @@
|
||||
# > [!IMPORTANT]
|
||||
|
||||
> dont forget to set up ps_configuration -> ps_ssl
|
||||
|
||||
## TODO
|
||||
|
||||
update price to show recommended retail price in shop, for back office keep price and add recommended retail price.
|
||||
fix stock, all products are out of stock
|
||||
BIN
ecomzone-v21.zip
Normal file
BIN
ecomzone-v21.zip
Normal file
Binary file not shown.
BIN
ecomzone-v22-mark3.7.zip
Normal file
BIN
ecomzone-v22-mark3.7.zip
Normal file
Binary file not shown.
61
ecomzone/README.md
Normal file
61
ecomzone/README.md
Normal file
@ -0,0 +1,61 @@
|
||||
# EcomZone PrestaShop Module - Troubleshooting
|
||||
|
||||
## Issue #1: 405 Method Not Allowed Error
|
||||
The module was encountering a 405 Method Not Allowed error with the message: "The ARRAY method is not supported for this route. Supported methods: GET, HEAD."
|
||||
|
||||
### Root Cause
|
||||
The issue was happening because the code was incorrectly passing parameters to the `makeRequest` method, causing the HTTP method to be set as an array instead of a string.
|
||||
|
||||
### Fixes Applied
|
||||
1. **Fixed `EcomZoneClient.php` Methods**:
|
||||
- Updated `getCatalog` to explicitly pass "GET" as the HTTP method
|
||||
- Updated `getProduct` to explicitly pass "GET" as the HTTP method
|
||||
|
||||
2. **Enhanced `makeApiRequest` Method**:
|
||||
- Added validation to ensure the method parameter is always a string
|
||||
- Added more detailed logging for debugging
|
||||
|
||||
3. **Fixed API Request Calls**:
|
||||
- Updated all calls to `makeApiRequest` to explicitly specify "GET" as the method
|
||||
|
||||
## Issue #2: Missing cURL Extension
|
||||
The module was unable to make API requests because the PHP cURL extension was not installed.
|
||||
|
||||
### Fix Applied
|
||||
- Installed the PHP cURL extension using: `sudo apt-get install php-curl`
|
||||
|
||||
## Testing Tools
|
||||
Two testing tools have been created to verify API connectivity:
|
||||
|
||||
1. **api_test.php**:
|
||||
- A standalone script that tests API connectivity without requiring PrestaShop
|
||||
- Verifies that the API token is valid and can retrieve product data
|
||||
|
||||
2. **get_token.php**:
|
||||
- Retrieves the API token from the PrestaShop configuration or database
|
||||
- Useful for troubleshooting API authentication issues
|
||||
|
||||
## Development Environment Testing
|
||||
When testing in a development environment without a full PrestaShop installation:
|
||||
|
||||
1. Use the standalone `api_test.php` script which doesn't require PrestaShop configuration files
|
||||
2. The regular `test_api.php` script requires access to PrestaShop's `config/config.inc.php` and `init.php` files
|
||||
3. Edit the paths in `test_api.php` if needed to point to your actual PrestaShop installation
|
||||
|
||||
## Production Environment Testing
|
||||
In a production environment:
|
||||
|
||||
1. Copy the module to your PrestaShop modules directory
|
||||
2. Install the module through the PrestaShop admin panel
|
||||
3. Configure your API credentials in the module settings
|
||||
4. Run the `test_api.php` script from within the module directory
|
||||
|
||||
## How to Verify
|
||||
1. Run the standalone API test script: `php -f api_test.php`
|
||||
2. If the API connection is working, you should see product data from the EcomZone API
|
||||
3. Check the module logs in `/log/ecomzone.log` for any new error messages
|
||||
|
||||
If you continue to experience issues, please check:
|
||||
- Your API token is valid and correctly configured
|
||||
- The API URL is correctly set to `https://dropship.ecomzone.eu/api`
|
||||
- Your web server has the required PHP extensions installed (cURL, JSON)
|
||||
143
ecomzone/api_test.php
Normal file
143
ecomzone/api_test.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
// Simple API test script that doesn't rely on PrestaShop configuration
|
||||
|
||||
// Display errors
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Define required constants
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
define('_PS_VERSION_', '8.0.0');
|
||||
}
|
||||
if (!defined('_PS_MODULE_DIR_')) {
|
||||
define('_PS_MODULE_DIR_', __DIR__ . '/../../modules/');
|
||||
}
|
||||
if (!defined('_PS_ROOT_DIR_')) {
|
||||
define('_PS_ROOT_DIR_', dirname(__DIR__, 2));
|
||||
}
|
||||
|
||||
// Include required files directly
|
||||
require_once(__DIR__ . '/classes/EcomZoneException.php');
|
||||
require_once(__DIR__ . '/classes/EcomZoneLogger.php');
|
||||
require_once(__DIR__ . '/classes/EcomZoneClient.php');
|
||||
|
||||
// Set headers for plain text output
|
||||
header('Content-Type: text/plain');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
|
||||
echo "EcomZone API Test (Standalone)\n";
|
||||
echo "============================\n\n";
|
||||
|
||||
// Create a mock Configuration class if it doesn't exist
|
||||
if (!class_exists('Configuration')) {
|
||||
class Configuration {
|
||||
public static function get($key, $default = null) {
|
||||
$config = [
|
||||
'ECOMZONE_API_URL' => 'https://dropship.ecomzone.eu/api',
|
||||
'ECOMZONE_API_TOKEN' => 'klRyAdrXaxL0s6PEUp7LDlH6T8aPSCtBY8NiEHsHiWpc6646K2TZPi5KMxUg' // Replace with your actual API token
|
||||
];
|
||||
return isset($config[$key]) ? $config[$key] : $default;
|
||||
}
|
||||
|
||||
public static function updateValue($key, $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock PrestaShop classes
|
||||
if (!class_exists('Context')) {
|
||||
class Context {
|
||||
public $shop;
|
||||
public static $instance;
|
||||
|
||||
public function __construct() {
|
||||
$this->shop = new stdClass();
|
||||
$this->shop->id = 1;
|
||||
}
|
||||
|
||||
public static function getContext() {
|
||||
if (!self::$instance) {
|
||||
self::$instance = new Context();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('PrestaShopLogger')) {
|
||||
class PrestaShopLogger {
|
||||
public static function addLog($message, $severity = 1, $error_code = null, $object_type = null, $object_id = null, $allow_duplicate = false, $id_employee = null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create log directory if it doesn't exist
|
||||
$logDir = __DIR__ . '/log';
|
||||
if (!file_exists($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get API settings
|
||||
$apiUrl = Configuration::get('ECOMZONE_API_URL');
|
||||
$apiToken = Configuration::get('ECOMZONE_API_TOKEN');
|
||||
|
||||
echo "API URL: " . $apiUrl . "\n";
|
||||
echo "API Token: " . (empty($apiToken) || $apiToken === 'YOUR_API_TOKEN' ?
|
||||
"Not set - Please edit api_test.php and replace YOUR_API_TOKEN with your actual token" :
|
||||
"Set (length: " . strlen($apiToken) . " chars)") . "\n\n";
|
||||
|
||||
if (empty($apiToken) || $apiToken === 'YOUR_API_TOKEN') {
|
||||
throw new Exception("API token is not configured. Please edit api_test.php and replace YOUR_API_TOKEN with your actual token.");
|
||||
}
|
||||
|
||||
echo "Testing API connection...\n\n";
|
||||
|
||||
// Create API client
|
||||
$client = new EcomZoneClient();
|
||||
|
||||
// Test API connectivity with catalog endpoint
|
||||
echo "Requesting catalog data (page 1, limit 1)...\n";
|
||||
$result = $client->getCatalog(1, 1);
|
||||
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
echo "Success! Received " . count($result['data']) . " product(s)\n";
|
||||
echo "Total products available: " . ($result['total'] ?? 'unknown') . "\n\n";
|
||||
|
||||
if (!empty($result['data'])) {
|
||||
$sku = $result['data'][0]['sku'] ?? null;
|
||||
|
||||
if ($sku) {
|
||||
echo "Testing product detail retrieval...\n";
|
||||
echo "Requesting product with SKU: " . $sku . "\n";
|
||||
|
||||
$productDetail = $client->getProduct($sku);
|
||||
|
||||
if (isset($productDetail['data']) && !empty($productDetail['data'])) {
|
||||
echo "Success! Retrieved product details\n";
|
||||
echo "Product name: " . ($productDetail['data']['product_name'] ?? 'unknown') . "\n";
|
||||
} else {
|
||||
echo "Error: Failed to retrieve product details\n";
|
||||
echo "Response: " . print_r($productDetail, true) . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "Error: Invalid catalog response\n";
|
||||
echo "Response: " . print_r($result, true) . "\n";
|
||||
}
|
||||
|
||||
echo "\nAPI test completed successfully!\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "ERROR: " . $e->getMessage() . "\n";
|
||||
|
||||
if ($e instanceof EcomZoneException) {
|
||||
echo "Error Code: " . $e->getCode() . "\n";
|
||||
}
|
||||
|
||||
echo "\nStack Trace:\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
@ -8,25 +8,16 @@ class EcomZoneClient
|
||||
private $apiToken;
|
||||
private $apiUrl;
|
||||
private $maxRetries = 3;
|
||||
private $timeout = 30;
|
||||
private $timeout = 60; // Increased timeout
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiToken = Configuration::get("ECOMZONE_API_TOKEN");
|
||||
$this->apiUrl = Configuration::get("ECOMZONE_API_URL");
|
||||
|
||||
if (empty($this->apiToken)) {
|
||||
throw new EcomZoneException(
|
||||
"API token not configured",
|
||||
EcomZoneException::API_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->apiUrl)) {
|
||||
throw new EcomZoneException(
|
||||
"API URL not configured",
|
||||
EcomZoneException::API_ERROR
|
||||
);
|
||||
$this->apiUrl = 'https://dropship.ecomzone.eu/api'; // Default API URL
|
||||
Configuration::updateValue("ECOMZONE_API_URL", $this->apiUrl);
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("API Client initialized", "INFO", [
|
||||
@ -34,44 +25,60 @@ class EcomZoneClient
|
||||
]);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$url = $this->apiUrl . "/catalog";
|
||||
$this->validateApiCredentials();
|
||||
|
||||
$params = [
|
||||
"page" => $page,
|
||||
"per_page" => $perPage,
|
||||
];
|
||||
|
||||
return $this->makeRequest("GET", $url, $params);
|
||||
return $this->makeRequest("catalog", "GET", $params);
|
||||
}
|
||||
|
||||
public function getProduct($sku)
|
||||
{
|
||||
$url = $this->apiUrl . "/product/" . urlencode($sku);
|
||||
return $this->makeRequest("GET", $url);
|
||||
$this->validateApiCredentials();
|
||||
|
||||
return $this->makeRequest("product/" . urlencode($sku), "GET");
|
||||
}
|
||||
|
||||
private function makeRequest($method, $url, $params = [], $data = null)
|
||||
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,
|
||||
"params" => $params,
|
||||
"retry" => $retryCount,
|
||||
"params" => $params
|
||||
]);
|
||||
|
||||
// Add query parameters
|
||||
if (!empty($params)) {
|
||||
$url .=
|
||||
(strpos($url, "?") === false ? "?" : "&") .
|
||||
http_build_query($params);
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
$options = [
|
||||
CURLOPT_URL => $url,
|
||||
@ -86,36 +93,108 @@ class EcomZoneClient
|
||||
"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) {
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
$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 ($error) {
|
||||
throw new Exception("cURL Error: " . $error);
|
||||
if ($errorNo > 0) {
|
||||
throw new Exception("cURL Error ({$errorNo}): " . $error);
|
||||
}
|
||||
|
||||
// Handle HTTP errors
|
||||
if ($httpCode >= 400) {
|
||||
throw new Exception(
|
||||
"HTTP Error: " . $httpCode . " Response: " . $response
|
||||
);
|
||||
$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");
|
||||
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;
|
||||
@ -126,10 +205,18 @@ class EcomZoneClient
|
||||
EcomZoneLogger::log("API request failed", "ERROR", [
|
||||
"error" => $e->getMessage(),
|
||||
"retry" => $retryCount,
|
||||
"url" => $url,
|
||||
"trace" => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
if ($retryCount < $this->maxRetries) {
|
||||
sleep(pow(2, $retryCount));
|
||||
$sleepTime = pow(2, $retryCount);
|
||||
EcomZoneLogger::log("Retrying request", "INFO", [
|
||||
"retry_count" => $retryCount,
|
||||
"sleep_time" => $sleepTime,
|
||||
"url" => $url
|
||||
]);
|
||||
sleep($sleepTime);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -142,11 +229,117 @@ class EcomZoneClient
|
||||
}
|
||||
} 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, '<!DOCTYPE html>') !== 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,66 +5,173 @@ if (!defined("_PS_VERSION_")) {
|
||||
|
||||
class EcomZoneLogger
|
||||
{
|
||||
private static $logFile = 'ecomzone.log';
|
||||
private static $maxLogSize = 10485760; // 10MB
|
||||
|
||||
public static function log($message, $level = "INFO", $context = [])
|
||||
const LOG_FILE = 'ecomzone.log';
|
||||
|
||||
// Log levels with corresponding severity
|
||||
const LEVELS = [
|
||||
'DEBUG' => 1,
|
||||
'INFO' => 2,
|
||||
'WARNING' => 3,
|
||||
'ERROR' => 4,
|
||||
'CRITICAL' => 5
|
||||
];
|
||||
|
||||
// Current log level - can be changed through configuration
|
||||
private static function getLogLevel()
|
||||
{
|
||||
$configLevel = Configuration::get('ECOMZONE_LOG_LEVEL', 'INFO');
|
||||
return isset(self::LEVELS[$configLevel]) ? $configLevel : 'INFO';
|
||||
}
|
||||
|
||||
public static function log($message, $level = 'INFO', $context = [])
|
||||
{
|
||||
try {
|
||||
$logDir = _PS_MODULE_DIR_ . 'ecomzone/log/';
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
// Check if we should log this level
|
||||
$logLevel = self::getLogLevel();
|
||||
if (self::LEVELS[$level] < self::LEVELS[$logLevel]) {
|
||||
return true; // Skip logging for less severe levels
|
||||
}
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$contextStr = !empty($context) ? json_encode($context) : '';
|
||||
|
||||
$logMessage = sprintf(
|
||||
"[%s] %s: %s %s\n",
|
||||
$timestamp,
|
||||
$level,
|
||||
$message,
|
||||
$contextStr
|
||||
);
|
||||
|
||||
file_put_contents(
|
||||
$logDir . self::$logFile,
|
||||
$logMessage,
|
||||
FILE_APPEND
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
// Fallback to PrestaShop's logger
|
||||
PrestaShopLogger::addLog(
|
||||
"EcomZone: $message",
|
||||
($level === 'ERROR' ? 3 : 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static function initLogFile()
|
||||
{
|
||||
if (!file_exists(self::$logFile)) {
|
||||
$logDir = dirname(self::$logFile);
|
||||
// Ensure the log directory exists
|
||||
$logDir = _PS_MODULE_DIR_ . 'ecomzone/log';
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
if (!@mkdir($logDir, 0755, true)) {
|
||||
throw new Exception("Failed to create log directory: " . $logDir);
|
||||
}
|
||||
@chmod($logDir, 0755); // Ensure the directory is writable
|
||||
}
|
||||
touch(self::$logFile);
|
||||
chmod(self::$logFile, 0666);
|
||||
|
||||
// Format the log entry
|
||||
$logEntry = [
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'level' => strtoupper($level),
|
||||
'message' => $message,
|
||||
'context' => $context
|
||||
];
|
||||
|
||||
// For errors and critical issues, also include a stack trace
|
||||
if (in_array($level, ['ERROR', 'CRITICAL']) && !isset($context['trace'])) {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
|
||||
$logEntry['trace'] = array_slice($trace, 1); // Skip the log function itself
|
||||
}
|
||||
|
||||
$logLine = json_encode($logEntry) . PHP_EOL;
|
||||
|
||||
// Rotate log file if it's too large (> 10MB)
|
||||
$logFile = $logDir . '/' . self::LOG_FILE;
|
||||
if (file_exists($logFile) && filesize($logFile) > 10 * 1024 * 1024) {
|
||||
self::rotateLogFile($logFile);
|
||||
}
|
||||
|
||||
// Write to log file with exclusive lock
|
||||
if (!@file_put_contents($logFile, $logLine, FILE_APPEND | LOCK_EX)) {
|
||||
throw new Exception("Failed to write to log file: " . $logFile);
|
||||
}
|
||||
|
||||
// Also write to PrestaShop log for error/critical levels
|
||||
if (in_array($level, ['ERROR', 'CRITICAL'])) {
|
||||
$psLogLevel = ($level === 'ERROR') ? 3 : 4;
|
||||
$contextStr = json_encode($context);
|
||||
PrestaShopLogger::addLog(
|
||||
"EcomZone: {$message} - Context: {$contextStr}",
|
||||
$psLogLevel,
|
||||
null,
|
||||
'EcomZone'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
// Last resort logging to PHP error log
|
||||
error_log('EcomZone logging error: ' . $e->getMessage() .
|
||||
'. Original message: ' . $message);
|
||||
|
||||
// Try to log to PrestaShop's log
|
||||
try {
|
||||
PrestaShopLogger::addLog(
|
||||
'EcomZone logging error: ' . $e->getMessage() . '. Original message: ' . $message,
|
||||
3, // Error level
|
||||
null,
|
||||
'EcomZone'
|
||||
);
|
||||
} catch (Exception $innerEx) {
|
||||
error_log('Failed to log to PrestaShop: ' . $innerEx->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static function rotateLogIfNeeded()
|
||||
private static function rotateLogFile($logFile)
|
||||
{
|
||||
if (!file_exists(self::$logFile)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$backup = $logFile . '.' . date('Y-m-d-H-i-s') . '.bak';
|
||||
if (!@rename($logFile, $backup)) {
|
||||
throw new Exception("Failed to rotate log file");
|
||||
}
|
||||
|
||||
if (filesize(self::$logFile) > self::$maxLogSize) {
|
||||
$backup = self::$logFile . "." . date("Y-m-d-H-i-s") . ".bak";
|
||||
rename(self::$logFile, $backup);
|
||||
touch(self::$logFile);
|
||||
chmod(self::$logFile, 0666);
|
||||
// Keep only last 5 backup files
|
||||
$backups = glob($logFile . '.*.bak');
|
||||
if (count($backups) > 5) {
|
||||
usort($backups, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
|
||||
$toDelete = array_slice($backups, 5);
|
||||
foreach ($toDelete as $file) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
error_log('EcomZone log rotation error: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLogPath()
|
||||
{
|
||||
// When running in standalone mode, use the local directory
|
||||
if (!defined('_PS_MODULE_DIR_') || !file_exists(_PS_MODULE_DIR_ . 'ecomzone')) {
|
||||
return __DIR__ . '/../log/' . self::LOG_FILE;
|
||||
}
|
||||
|
||||
return _PS_MODULE_DIR_ . 'ecomzone/log/' . self::LOG_FILE;
|
||||
}
|
||||
|
||||
public static function getLogContents($maxLines = 100)
|
||||
{
|
||||
$logFile = self::getLogPath();
|
||||
if (!file_exists($logFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$logs = [];
|
||||
$lines = array_reverse(file($logFile));
|
||||
$count = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ($count >= $maxLines) break;
|
||||
|
||||
$entry = json_decode($line, true);
|
||||
if ($entry) {
|
||||
$logs[] = $entry;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
public static function clearLogs()
|
||||
{
|
||||
$logFile = self::getLogPath();
|
||||
if (file_exists($logFile)) {
|
||||
@unlink($logFile);
|
||||
self::log("Log file cleared", "INFO");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,8 @@ if (!defined("_PS_VERSION_")) {
|
||||
exit();
|
||||
}
|
||||
|
||||
require_once dirname(__FILE__) . '/interfaces/IProductSync.php';
|
||||
|
||||
class EcomZoneProductSync implements IProductSync
|
||||
{
|
||||
private EcomZoneClient $client;
|
||||
@ -22,63 +24,112 @@ class EcomZoneProductSync implements IProductSync
|
||||
|
||||
public function importProducts(int $perPage = 100): array
|
||||
{
|
||||
$page = 1;
|
||||
$totalImported = 0;
|
||||
$totalAvailable = 0;
|
||||
$errors = [];
|
||||
|
||||
try {
|
||||
EcomZoneLogger::log("Starting product import", "INFO", [
|
||||
// Get or initialize sync state
|
||||
$syncState = $this->getSyncState();
|
||||
|
||||
// If this is a new sync (page 1 and no imports), reset everything
|
||||
if (empty($syncState) || ($syncState['current_page'] ?? 1) === 1 && ($syncState['total_imported'] ?? 0) === 0) {
|
||||
$this->resetSyncState();
|
||||
$syncState = [];
|
||||
}
|
||||
|
||||
$page = $syncState['current_page'] ?? 1;
|
||||
$totalImported = $syncState['total_imported'] ?? 0;
|
||||
$totalAvailable = $syncState['total_available'] ?? 0;
|
||||
$errors = $syncState['errors'] ?? [];
|
||||
$maxProductsPerBatch = 20; // Process 20 products at a time to avoid timeout
|
||||
|
||||
EcomZoneLogger::log("Starting/Resuming product import", "INFO", [
|
||||
"page" => $page,
|
||||
"per_page" => $perPage,
|
||||
"total_imported" => $totalImported,
|
||||
"is_new_sync" => empty($syncState)
|
||||
]);
|
||||
|
||||
do {
|
||||
$catalog = $this->client->getCatalog($page, $perPage);
|
||||
// Get catalog for current page
|
||||
$catalog = $this->client->getCatalog($page, $perPage);
|
||||
|
||||
if (!isset($catalog["data"]) || !is_array($catalog["data"])) {
|
||||
throw new EcomZoneException(
|
||||
"Invalid catalog response",
|
||||
EcomZoneException::API_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($catalog["data"] as $product) {
|
||||
try {
|
||||
if ($this->importSingleProduct($product)) {
|
||||
$totalImported++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$errors[] = [
|
||||
"sku" => $product["sku"] ?? "unknown",
|
||||
"error" => $e->getMessage(),
|
||||
];
|
||||
EcomZoneLogger::log("Product import failed", "ERROR", [
|
||||
"sku" => $product["sku"] ?? "unknown",
|
||||
"error" => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (!isset($catalog["data"]) || !is_array($catalog["data"])) {
|
||||
throw new EcomZoneException(
|
||||
"Invalid catalog response",
|
||||
EcomZoneException::API_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
// Update total available on first page or if it changes
|
||||
if ($page === 1 || ($catalog["total"] ?? 0) !== $totalAvailable) {
|
||||
$totalAvailable = $catalog["total"] ?? 0;
|
||||
$page++;
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("Page processed", "INFO", [
|
||||
"page" => $page - 1,
|
||||
"imported" => $totalImported,
|
||||
"total" => $totalAvailable,
|
||||
]);
|
||||
} while (
|
||||
isset($catalog["next_page_url"]) &&
|
||||
$catalog["next_page_url"] !== null
|
||||
);
|
||||
$productsProcessed = 0;
|
||||
$batchErrors = [];
|
||||
|
||||
// Process only a batch of products
|
||||
foreach ($catalog["data"] as $product) {
|
||||
if ($productsProcessed >= $maxProductsPerBatch) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->clearCache();
|
||||
try {
|
||||
if ($this->importSingleProduct($product)) {
|
||||
$totalImported++;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$batchErrors[] = [
|
||||
"sku" => $product["sku"] ?? "unknown",
|
||||
"error" => $e->getMessage(),
|
||||
];
|
||||
EcomZoneLogger::log("Product import failed", "ERROR", [
|
||||
"sku" => $product["sku"] ?? "unknown",
|
||||
"error" => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
$productsProcessed++;
|
||||
}
|
||||
|
||||
// Update errors array with new batch errors
|
||||
$errors = array_merge($errors, $batchErrors);
|
||||
|
||||
// Calculate progress
|
||||
$progress = [
|
||||
'current_page' => $page,
|
||||
'total_imported' => $totalImported,
|
||||
'total_available' => $totalAvailable,
|
||||
'errors' => $errors,
|
||||
'is_complete' => false,
|
||||
'products_processed_on_page' => $productsProcessed
|
||||
];
|
||||
|
||||
// Check if we need to continue to next page
|
||||
if ($productsProcessed < count($catalog["data"])) {
|
||||
// Still more products to process on this page
|
||||
$progress['products_processed_on_page'] = $productsProcessed;
|
||||
} else {
|
||||
// Move to next page if available
|
||||
if (isset($catalog["next_page_url"]) && $catalog["next_page_url"] !== null) {
|
||||
$progress['current_page'] = $page + 1;
|
||||
$progress['products_processed_on_page'] = 0;
|
||||
} else {
|
||||
$progress['is_complete'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save progress
|
||||
$this->saveSyncState($progress);
|
||||
|
||||
// Clear cache if complete
|
||||
if ($progress['is_complete']) {
|
||||
$this->clearCache();
|
||||
}
|
||||
|
||||
return [
|
||||
"success" => true,
|
||||
"imported" => $totalImported,
|
||||
"total" => $totalAvailable,
|
||||
"errors" => $errors,
|
||||
"is_complete" => $progress['is_complete'],
|
||||
"current_page" => $progress['current_page']
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Product import process failed", "ERROR", [
|
||||
@ -192,8 +243,11 @@ class EcomZoneProductSync implements IProductSync
|
||||
$product->name[$this->defaultLangId] = $data['product_name'];
|
||||
$product->description[$this->defaultLangId] = $data['long_description'] ?? $data['description'];
|
||||
$product->description_short[$this->defaultLangId] = $data['description'];
|
||||
$product->price = (float)$data['product_price'];
|
||||
$product->wholesale_price = (float)($data['recommended_retail_price'] ?? 0);
|
||||
|
||||
// Update prices
|
||||
$product->wholesale_price = (float)$data['product_price']; // Original price as wholesale price
|
||||
$product->price = (float)($data['recommended_retail_price'] ?? $data['product_price']); // Recommended retail price for shop display
|
||||
|
||||
$product->reference = $data['sku'];
|
||||
$product->ean13 = $data['ean'] ?? '';
|
||||
|
||||
@ -354,15 +408,32 @@ class EcomZoneProductSync implements IProductSync
|
||||
private function updateProductStock(Product $product, array $data): void
|
||||
{
|
||||
try {
|
||||
$quantity = (int) ($data['stock_quantity'] ?? 0);
|
||||
StockAvailable::setQuantity($product->id, 0, $quantity);
|
||||
$quantity = (int)($data['stock'] ?? 0);
|
||||
|
||||
// Make sure product is saved first
|
||||
if (!$product->id) {
|
||||
$product->save();
|
||||
}
|
||||
|
||||
// Update stock quantity
|
||||
StockAvailable::setQuantity(
|
||||
$product->id,
|
||||
0, // Combination ID (0 for products without combinations)
|
||||
$quantity
|
||||
);
|
||||
|
||||
// Update out of stock behavior
|
||||
StockAvailable::setProductOutOfStock(
|
||||
$product->id,
|
||||
$quantity > 0 ? 2 : 0, // 2 = Allow orders, 0 = Deny orders
|
||||
2, // Always allow orders
|
||||
$this->defaultShopId
|
||||
);
|
||||
|
||||
EcomZoneLogger::log("Stock updated", "DEBUG", [
|
||||
"product" => $product->reference,
|
||||
"quantity" => $quantity
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to update stock", "WARNING", [
|
||||
"product" => $product->reference,
|
||||
@ -375,49 +446,126 @@ class EcomZoneProductSync implements IProductSync
|
||||
{
|
||||
try {
|
||||
$imageUrls = [];
|
||||
|
||||
EcomZoneLogger::log("Raw product data for images", "DEBUG", [
|
||||
"product" => $product->reference,
|
||||
"data" => json_encode($data)
|
||||
]);
|
||||
|
||||
// Process main image
|
||||
if (!empty($data["image"])) {
|
||||
$imageUrls[] = $data["image"];
|
||||
}
|
||||
|
||||
// Process additional images
|
||||
if (!empty($data["images"])) {
|
||||
$additionalImages = json_decode($data["images"], true);
|
||||
if (is_array($additionalImages)) {
|
||||
$imageUrls = array_merge($imageUrls, $additionalImages);
|
||||
// Handle different possible data structures
|
||||
if (isset($data['images']) && is_string($data['images'])) {
|
||||
$images = json_decode($data['images'], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($images)) {
|
||||
$imageUrls = array_merge($imageUrls, $images);
|
||||
}
|
||||
} elseif (isset($data['images']) && is_array($data['images'])) {
|
||||
$imageUrls = array_merge($imageUrls, $data['images']);
|
||||
}
|
||||
|
||||
// Add main image if available
|
||||
if (!empty($data['image'])) {
|
||||
array_unshift($imageUrls, $data['image']); // Add as first image
|
||||
}
|
||||
|
||||
// Filter out empty or invalid URLs
|
||||
$imageUrls = array_filter($imageUrls, function($url) {
|
||||
return !empty($url) && filter_var($url, FILTER_VALIDATE_URL);
|
||||
});
|
||||
|
||||
if (empty($imageUrls)) {
|
||||
EcomZoneLogger::log("No valid images found for product", "WARNING", [
|
||||
"product" => $product->reference,
|
||||
"data" => json_encode($data)
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete existing images if any
|
||||
$product->deleteImages();
|
||||
|
||||
$successCount = 0;
|
||||
foreach ($imageUrls as $index => $imageUrl) {
|
||||
$this->importProductImage($product, $imageUrl, $index === 0);
|
||||
try {
|
||||
EcomZoneLogger::log("Processing image", "DEBUG", [
|
||||
"product" => $product->reference,
|
||||
"image_url" => $imageUrl,
|
||||
"index" => $index
|
||||
]);
|
||||
|
||||
$this->importProductImage($product, $imageUrl, $index === 0);
|
||||
$successCount++;
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to process image", "WARNING", [
|
||||
"product" => $product->reference,
|
||||
"image_url" => $imageUrl,
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($successCount === 0 && !empty($imageUrls)) {
|
||||
throw new Exception(sprintf(
|
||||
"Failed to process any images for the product. Attempted %d images.",
|
||||
count($imageUrls)
|
||||
));
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("Processed product images", "INFO", [
|
||||
"product" => $product->reference,
|
||||
"total_images" => count($imageUrls),
|
||||
"successful_imports" => $successCount
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to process product images", "WARNING", [
|
||||
EcomZoneLogger::log("Failed to process product images", "ERROR", [
|
||||
"sku" => $product->reference,
|
||||
"error" => $e->getMessage(),
|
||||
"data" => json_encode($data)
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function importProductImage(Product $product, string $imageUrl, bool $cover = false): void
|
||||
{
|
||||
try {
|
||||
// Create temporary file
|
||||
$tempFile = tempnam(_PS_TMP_IMG_DIR_, 'import_');
|
||||
// Create temporary directory if it doesn't exist
|
||||
$tempDir = _PS_MODULE_DIR_ . 'ecomzone/tmp/';
|
||||
if (!is_dir($tempDir)) {
|
||||
mkdir($tempDir, 0755, true);
|
||||
chmod($tempDir, 0755);
|
||||
}
|
||||
|
||||
// Clean URL - handle double escaped slashes
|
||||
$cleanUrl = stripslashes(stripslashes(trim($imageUrl)));
|
||||
|
||||
// Download image
|
||||
if (!copy($imageUrl, $tempFile)) {
|
||||
throw new Exception("Failed to download image: " . $imageUrl);
|
||||
EcomZoneLogger::log("Processing image URL", "DEBUG", [
|
||||
"product" => $product->reference,
|
||||
"original_url" => $imageUrl,
|
||||
"cleaned_url" => $cleanUrl
|
||||
]);
|
||||
|
||||
// Create temp file
|
||||
$tempFile = tempnam($tempDir, 'import_');
|
||||
if ($tempFile) {
|
||||
chmod($tempFile, 0644);
|
||||
}
|
||||
|
||||
// Use the client class to download the image
|
||||
$imageResult = $this->client->downloadImage($cleanUrl);
|
||||
$imageData = $imageResult['data'];
|
||||
$contentType = $imageResult['content_type'];
|
||||
|
||||
// Save image data to temp file
|
||||
file_put_contents($tempFile, $imageData);
|
||||
|
||||
// Verify the downloaded file is a valid image
|
||||
$imageInfo = @getimagesize($tempFile);
|
||||
if (!$imageInfo) {
|
||||
throw new Exception("Downloaded file is not a valid image. Content type: $contentType");
|
||||
}
|
||||
|
||||
// Create image object
|
||||
$image = new Image();
|
||||
$image->id_product = $product->id;
|
||||
@ -433,29 +581,60 @@ class EcomZoneProductSync implements IProductSync
|
||||
$imageDir = dirname($imagePath);
|
||||
|
||||
if (!file_exists($imageDir)) {
|
||||
mkdir($imageDir, 0755, true);
|
||||
if (!mkdir($imageDir, 0755, true)) {
|
||||
throw new Exception("Failed to create image directory: " . $imageDir);
|
||||
}
|
||||
chmod($imageDir, 0755);
|
||||
}
|
||||
|
||||
// Generate image
|
||||
if (!ImageManager::resize(
|
||||
$tempFile,
|
||||
$imagePath . '.jpg'
|
||||
$imagePath . '.jpg',
|
||||
null,
|
||||
null,
|
||||
'jpg'
|
||||
)) {
|
||||
throw new Exception("Failed to process image");
|
||||
}
|
||||
|
||||
// Ensure the generated image has correct permissions
|
||||
chmod($imagePath . '.jpg', 0644);
|
||||
|
||||
// Generate thumbnails
|
||||
$this->generateThumbnails($image);
|
||||
|
||||
// Clean up
|
||||
unlink($tempFile);
|
||||
if (file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("Image imported successfully", "DEBUG", [
|
||||
"product" => $product->reference,
|
||||
"image_url" => $cleanUrl,
|
||||
"mime_type" => $imageInfo['mime']
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to import product image", "ERROR", [
|
||||
"product" => $product->reference,
|
||||
"image_url" => $imageUrl,
|
||||
"error" => $e->getMessage()
|
||||
"clean_url" => $cleanUrl ?? '',
|
||||
"error" => $e->getMessage(),
|
||||
"content_type" => $contentType ?? null
|
||||
]);
|
||||
|
||||
// Clean up on error
|
||||
if (isset($tempFile) && file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
|
||||
// Delete image record if it was created
|
||||
if (isset($image) && $image->id) {
|
||||
$image->delete();
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,117 +642,41 @@ class EcomZoneProductSync implements IProductSync
|
||||
{
|
||||
try {
|
||||
$imageTypes = ImageType::getImagesTypes('products');
|
||||
$sourceFile = _PS_PROD_IMG_DIR_ . $image->getExistingImgPath() . '.jpg';
|
||||
|
||||
if (!file_exists($sourceFile)) {
|
||||
throw new Exception("Source image file not found");
|
||||
}
|
||||
|
||||
foreach ($imageTypes as $imageType) {
|
||||
$dir = _PS_PROD_IMG_DIR_ . $image->getExistingImgPath();
|
||||
$destination = _PS_PROD_IMG_DIR_ . $image->getExistingImgPath() . '-' . stripslashes($imageType['name']) . '.jpg';
|
||||
$dir = dirname($destination);
|
||||
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
throw new Exception("Failed to create thumbnail directory: " . $dir);
|
||||
}
|
||||
chmod($dir, 0755);
|
||||
}
|
||||
ImageManager::resize(
|
||||
_PS_PROD_IMG_DIR_ . $image->getExistingImgPath() . '.jpg',
|
||||
_PS_PROD_IMG_DIR_ . $image->getExistingImgPath() . '-' . stripslashes($imageType['name']) . '.jpg',
|
||||
|
||||
if (!ImageManager::resize(
|
||||
$sourceFile,
|
||||
$destination,
|
||||
(int)$imageType['width'],
|
||||
(int)$imageType['height']
|
||||
);
|
||||
)) {
|
||||
throw new Exception("Failed to generate thumbnail: " . $imageType['name']);
|
||||
}
|
||||
|
||||
// Set proper permissions for the thumbnail
|
||||
chmod($destination, 0644);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to generate thumbnails", "WARNING", [
|
||||
"image_id" => $image->id,
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function generateImageType(
|
||||
string $srcPath,
|
||||
string $destPath,
|
||||
?int $width,
|
||||
?int $height
|
||||
): void {
|
||||
try {
|
||||
$this->createDirectoryIfNotExists(dirname($destPath));
|
||||
|
||||
if ($width === null || $height === null) {
|
||||
copy($srcPath, $destPath);
|
||||
return;
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($srcPath);
|
||||
if (!$imageInfo) {
|
||||
throw new Exception("Invalid image file");
|
||||
}
|
||||
|
||||
$srcImage = $this->createImageFromType($srcPath, $imageInfo[2]);
|
||||
$destImage = imagecreatetruecolor($width, $height);
|
||||
|
||||
// Preserve transparency for PNG images
|
||||
if ($imageInfo[2] === IMAGETYPE_PNG) {
|
||||
imagealphablending($destImage, false);
|
||||
imagesavealpha($destImage, true);
|
||||
$transparent = imagecolorallocatealpha(
|
||||
$destImage,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
127
|
||||
);
|
||||
imagefilledrectangle(
|
||||
$destImage,
|
||||
0,
|
||||
0,
|
||||
$width,
|
||||
$height,
|
||||
$transparent
|
||||
);
|
||||
}
|
||||
|
||||
imagecopyresampled(
|
||||
$destImage,
|
||||
$srcImage,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$width,
|
||||
$height,
|
||||
$imageInfo[0],
|
||||
$imageInfo[1]
|
||||
);
|
||||
|
||||
imagejpeg($destImage, $destPath, 95);
|
||||
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($destImage);
|
||||
} catch (Exception $e) {
|
||||
throw new EcomZoneException(
|
||||
"Failed to generate image type: " . $e->getMessage(),
|
||||
EcomZoneException::IMAGE_PROCESSING_ERROR,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function createImageFromType(string $filename, int $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
return imagecreatefromjpeg($filename);
|
||||
case IMAGETYPE_PNG:
|
||||
return imagecreatefrompng($filename);
|
||||
case IMAGETYPE_GIF:
|
||||
return imagecreatefromgif($filename);
|
||||
default:
|
||||
throw new Exception("Unsupported image type: " . $type);
|
||||
}
|
||||
}
|
||||
|
||||
private function createDirectoryIfNotExists(string $directory): void
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
if (!mkdir($directory, 0755, true)) {
|
||||
throw new Exception(
|
||||
"Failed to create directory: " . $directory
|
||||
);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -716,6 +819,27 @@ class EcomZoneProductSync implements IProductSync
|
||||
Media::clearCache();
|
||||
PrestaShopAutoload::getInstance()->generateIndex();
|
||||
}
|
||||
|
||||
private function getSyncState(): array
|
||||
{
|
||||
$state = Configuration::get('ECOMZONE_SYNC_STATE');
|
||||
return $state ? json_decode($state, true) : [];
|
||||
}
|
||||
|
||||
private function saveSyncState(array $state): void
|
||||
{
|
||||
Configuration::updateValue('ECOMZONE_SYNC_STATE', json_encode($state));
|
||||
}
|
||||
|
||||
public function resetSyncState(): void
|
||||
{
|
||||
// Delete both sync state and progress
|
||||
Configuration::deleteByName('ECOMZONE_SYNC_STATE');
|
||||
Configuration::deleteByName('ECOMZONE_SYNC_PROGRESS');
|
||||
Configuration::deleteByName('ECOMZONE_LAST_SYNC');
|
||||
|
||||
EcomZoneLogger::log("Sync state reset", "INFO");
|
||||
}
|
||||
}
|
||||
|
||||
$token = Configuration::get('ECOMZONE_CRON_TOKEN');
|
||||
|
||||
@ -5,6 +5,17 @@ if (!defined('_PS_VERSION_')) {
|
||||
|
||||
interface IProductSync
|
||||
{
|
||||
/**
|
||||
* Import products from external source
|
||||
* @param int $perPage Number of products to import per page
|
||||
* @return array Import results containing success status, counts, and any errors
|
||||
*/
|
||||
public function importProducts(int $perPage = 100): array;
|
||||
|
||||
/**
|
||||
* Import a single product
|
||||
* @param array $productData Product data to import
|
||||
* @return bool Success status
|
||||
*/
|
||||
public function importSingleProduct(array $productData): bool;
|
||||
}
|
||||
@ -1,123 +0,0 @@
|
||||
<?php
|
||||
ob_start(); // Start output buffering
|
||||
|
||||
// Include PrestaShop configuration
|
||||
require_once dirname(__FILE__) . "/../../config/config.inc.php";
|
||||
require_once dirname(__FILE__) . "/../../init.php";
|
||||
|
||||
// Autoloader for module classes
|
||||
spl_autoload_register(function ($className) {
|
||||
$classMap = [
|
||||
'EcomZoneException' => 'classes/EcomZoneException.php',
|
||||
'EcomZoneClient' => 'classes/EcomZoneClient.php',
|
||||
'EcomZoneLogger' => 'classes/EcomZoneLogger.php',
|
||||
'EcomZoneProductSync' => 'classes/EcomZoneProductSync.php',
|
||||
'EcomZoneProductImport' => 'classes/EcomZoneProductImport.php',
|
||||
'EcomZoneCategoryHandler' => 'classes/EcomZoneCategoryHandler.php',
|
||||
'IProductSync' => 'classes/interfaces/IProductSync.php',
|
||||
'ICategory' => 'classes/interfaces/ICategory.php'
|
||||
];
|
||||
|
||||
if (isset($classMap[$className])) {
|
||||
require_once dirname(__FILE__) . '/' . $classMap[$className];
|
||||
}
|
||||
});
|
||||
|
||||
// Function to send JSON response
|
||||
function sendJsonResponse($data, $statusCode = 200)
|
||||
{
|
||||
if (php_sapi_name() === 'cli') {
|
||||
echo json_encode($data, JSON_PRETTY_PRINT) . "\n";
|
||||
} else {
|
||||
if (!headers_sent()) {
|
||||
http_response_code($statusCode);
|
||||
header("Content-Type: application/json");
|
||||
}
|
||||
echo json_encode($data);
|
||||
}
|
||||
exit($statusCode === 200 ? 0 : 1);
|
||||
}
|
||||
|
||||
// Get token from either GET parameter or command line argument
|
||||
$token = '';
|
||||
if (php_sapi_name() === 'cli') {
|
||||
global $argv;
|
||||
foreach ($argv as $arg) {
|
||||
if (strpos($arg, 'token=') === 0) {
|
||||
$token = substr($arg, 6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$token = Tools::getValue('token');
|
||||
}
|
||||
|
||||
// Security check
|
||||
$configToken = Configuration::get("ECOMZONE_CRON_TOKEN");
|
||||
|
||||
if (empty($token) || $token !== $configToken) {
|
||||
sendJsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Invalid token',
|
||||
'provided_token' => $token,
|
||||
'expected_token' => $configToken
|
||||
], 403);
|
||||
}
|
||||
|
||||
try {
|
||||
// Initialize logger
|
||||
EcomZoneLogger::log("Starting cron execution", "INFO", [
|
||||
"time" => date("Y-m-d H:i:s"),
|
||||
]);
|
||||
|
||||
// Check last run time to prevent too frequent execution
|
||||
$lastRun = Configuration::get("ECOMZONE_LAST_CRON_RUN");
|
||||
$minInterval = 360;
|
||||
|
||||
if (!empty($lastRun) && strtotime($lastRun) + $minInterval > time()) {
|
||||
EcomZoneLogger::log("Skipping cron - too soon since last run", "INFO", [
|
||||
"last_run" => $lastRun,
|
||||
"next_run" => date("Y-m-d H:i:s", strtotime($lastRun) + $minInterval),
|
||||
]);
|
||||
|
||||
sendJsonResponse([
|
||||
"success" => false,
|
||||
"message" => "Too soon since last run",
|
||||
"last_run" => $lastRun,
|
||||
"next_run" => date("Y-m-d H:i:s", strtotime($lastRun) + $minInterval),
|
||||
]);
|
||||
}
|
||||
|
||||
// Initialize Product Sync
|
||||
$productSync = new EcomZoneProductSync();
|
||||
|
||||
// Start the import process
|
||||
$result = $productSync->importProducts(100); // Import 100 products per page
|
||||
|
||||
// Update last run time
|
||||
Configuration::updateValue("ECOMZONE_LAST_CRON_RUN", date("Y-m-d H:i:s"));
|
||||
|
||||
// Log completion and send response
|
||||
EcomZoneLogger::log("Cron execution completed", "INFO", [
|
||||
"imported" => $result["imported"],
|
||||
"total" => $result["total"],
|
||||
"errors" => count($result["errors"]),
|
||||
]);
|
||||
|
||||
sendJsonResponse([
|
||||
"success" => true,
|
||||
"result" => $result,
|
||||
"timestamp" => date("Y-m-d H:i:s"),
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Cron execution failed", "ERROR", [
|
||||
"error" => $e->getMessage(),
|
||||
"trace" => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
sendJsonResponse([
|
||||
"success" => false,
|
||||
"error" => $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
|
||||
@ -20,6 +20,9 @@ class Ecomzone extends Module
|
||||
|
||||
parent::__construct();
|
||||
|
||||
// Register autoloader
|
||||
spl_autoload_register([$this, 'autoload']);
|
||||
|
||||
$this->displayName = $this->trans(
|
||||
"EcomZone Dropshipping",
|
||||
[],
|
||||
@ -37,78 +40,462 @@ class Ecomzone extends Module
|
||||
);
|
||||
}
|
||||
|
||||
public function install()
|
||||
/**
|
||||
* Autoload EcomZone classes
|
||||
*/
|
||||
public function autoload($className)
|
||||
{
|
||||
if (!parent::install()
|
||||
|| !$this->registerHook('actionCronJob')
|
||||
|| !$this->registerHook('actionProductUpdate')
|
||||
|| !$this->registerHook('actionProductDelete')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set default configuration values
|
||||
Configuration::updateValue('ECOMZONE_API_URL', 'https://dropship.ecomzone.eu/api');
|
||||
Configuration::updateValue('ECOMZONE_CRON_TOKEN', Tools::encrypt(uniqid()));
|
||||
Configuration::updateValue('ECOMZONE_LAST_SYNC', '');
|
||||
|
||||
// Create required directories
|
||||
$this->createRequiredDirectories();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function createRequiredDirectories()
|
||||
{
|
||||
$dirs = [
|
||||
_PS_MODULE_DIR_ . $this->name . '/log',
|
||||
_PS_MODULE_DIR_ . $this->name . '/tmp'
|
||||
// Define class mapping
|
||||
$classMap = [
|
||||
'EcomZoneException' => 'classes/EcomZoneException.php',
|
||||
'EcomZoneClient' => 'classes/EcomZoneClient.php',
|
||||
'EcomZoneLogger' => 'classes/EcomZoneLogger.php',
|
||||
'EcomZoneProductSync' => 'classes/EcomZoneProductSync.php',
|
||||
'EcomZoneProductImport' => 'classes/EcomZoneProductImport.php',
|
||||
'EcomZoneCategoryHandler' => 'classes/EcomZoneCategoryHandler.php',
|
||||
'IProductSync' => 'classes/interfaces/IProductSync.php',
|
||||
'ICategory' => 'classes/interfaces/ICategory.php'
|
||||
];
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (!file_exists($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
// Check if the class exists in our map
|
||||
if (isset($classMap[$className])) {
|
||||
$file = _PS_MODULE_DIR_ . $this->name . '/' . $classMap[$className];
|
||||
if (file_exists($file)) {
|
||||
require_once($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setImageDirectoryPermissions(): bool
|
||||
{
|
||||
try {
|
||||
$directories = [
|
||||
_PS_IMG_DIR_,
|
||||
_PS_PROD_IMG_DIR_,
|
||||
_PS_MODULE_DIR_ . $this->name . '/tmp/',
|
||||
_PS_MODULE_DIR_ . $this->name . '/log/'
|
||||
];
|
||||
|
||||
EcomZoneLogger::log("Setting up directory permissions", "INFO");
|
||||
|
||||
foreach ($directories as $dir) {
|
||||
if (!file_exists($dir)) {
|
||||
EcomZoneLogger::log("Creating directory", "INFO", ['directory' => $dir]);
|
||||
if (!@mkdir($dir, 0755, true)) {
|
||||
$error = error_get_last();
|
||||
throw new Exception("Failed to create directory {$dir}: " . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable($dir)) {
|
||||
// Get current permissions
|
||||
$currentPerms = fileperms($dir) & 0777;
|
||||
EcomZoneLogger::log("Directory not writable, setting permissions", "INFO", [
|
||||
'directory' => $dir,
|
||||
'current_perms' => decoct($currentPerms),
|
||||
'target_perms' => '0755'
|
||||
]);
|
||||
|
||||
if (!@chmod($dir, 0755)) {
|
||||
$error = error_get_last();
|
||||
throw new Exception("Failed to set permissions for {$dir}: " . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the product image directory, ensure all subdirectories are writable
|
||||
if (strpos($dir, _PS_PROD_IMG_DIR_) === 0) {
|
||||
EcomZoneLogger::log("Processing product image subdirectories", "INFO", ['directory' => $dir]);
|
||||
|
||||
try {
|
||||
if (!is_dir($dir)) {
|
||||
continue; // Skip if directory doesn't exist
|
||||
}
|
||||
|
||||
$iterator = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
RecursiveIteratorIterator::SELF_FIRST
|
||||
);
|
||||
|
||||
foreach ($iterator as $item) {
|
||||
if ($item->isDir()) {
|
||||
if (!is_writable($item->getPathname())) {
|
||||
if (!@chmod($item->getPathname(), 0755)) {
|
||||
$error = error_get_last();
|
||||
EcomZoneLogger::log("Failed to set subdirectory permissions", "WARNING", [
|
||||
'directory' => $item->getPathname(),
|
||||
'error' => $error['message'] ?? 'Unknown error'
|
||||
]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is_writable($item->getPathname())) {
|
||||
if (!@chmod($item->getPathname(), 0644)) {
|
||||
$error = error_get_last();
|
||||
EcomZoneLogger::log("Failed to set file permissions", "WARNING", [
|
||||
'file' => $item->getPathname(),
|
||||
'error' => $error['message'] ?? 'Unknown error'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// Log but don't fail if we can't read a subdirectory
|
||||
EcomZoneLogger::log("Failed to process subdirectory", "WARNING", [
|
||||
'directory' => $dir,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("Directory permissions setup complete", "INFO");
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to set directory permissions", "ERROR", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function install()
|
||||
{
|
||||
EcomZoneLogger::log("Starting module installation", "INFO");
|
||||
|
||||
try {
|
||||
// First check if module directory exists and is writable
|
||||
if (!is_dir(_PS_MODULE_DIR_ . $this->name)) {
|
||||
EcomZoneLogger::log("Creating module directory", "INFO");
|
||||
if (!@mkdir(_PS_MODULE_DIR_ . $this->name, 0755, true)) {
|
||||
$error = error_get_last();
|
||||
throw new Exception('Failed to create module directory: ' . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
// Create required directories first
|
||||
$dirs = [
|
||||
_PS_MODULE_DIR_ . $this->name . '/log',
|
||||
_PS_MODULE_DIR_ . $this->name . '/tmp',
|
||||
_PS_IMG_DIR_,
|
||||
_PS_PROD_IMG_DIR_
|
||||
];
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (!file_exists($dir)) {
|
||||
EcomZoneLogger::log("Creating directory: " . $dir, "INFO");
|
||||
if (!@mkdir($dir, 0755, true)) {
|
||||
$error = error_get_last();
|
||||
throw new Exception('Failed to create directory: ' . $dir . ' - ' . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable($dir)) {
|
||||
EcomZoneLogger::log("Setting directory writable: " . $dir, "INFO");
|
||||
if (!@chmod($dir, 0755)) {
|
||||
$error = error_get_last();
|
||||
throw new Exception('Failed to set permissions for directory: ' . $dir . ' - ' . ($error['message'] ?? 'Unknown error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up database tables
|
||||
EcomZoneLogger::log("Setting up database tables", "INFO");
|
||||
$this->createTables();
|
||||
|
||||
// Install parent module
|
||||
EcomZoneLogger::log("Installing parent module", "INFO");
|
||||
if (!parent::install()) {
|
||||
throw new Exception('Failed to install parent module');
|
||||
}
|
||||
|
||||
// Register hooks
|
||||
EcomZoneLogger::log("Registering hooks", "INFO");
|
||||
if (!$this->registerHook('actionProductUpdate')
|
||||
|| !$this->registerHook('actionProductDelete')
|
||||
|| !$this->registerHook('actionCronJob')) {
|
||||
throw new Exception('Failed to register hooks');
|
||||
}
|
||||
|
||||
// Set default configuration values
|
||||
EcomZoneLogger::log("Setting default configuration values", "INFO");
|
||||
$defaultConfig = [
|
||||
'ECOMZONE_API_URL' => 'https://dropship.ecomzone.eu/api',
|
||||
'ECOMZONE_API_TOKEN' => '',
|
||||
'ECOMZONE_LAST_SYNC' => '',
|
||||
'ECOMZONE_LOG_LEVEL' => 'INFO',
|
||||
'ECOMZONE_CRON_TOKEN' => Tools::passwdGen(32) // Generate random token for cron
|
||||
];
|
||||
|
||||
foreach ($defaultConfig as $key => $value) {
|
||||
if (!Configuration::updateValue($key, $value)) {
|
||||
throw new Exception('Failed to set configuration value: ' . $key);
|
||||
}
|
||||
}
|
||||
|
||||
// Set directory permissions
|
||||
EcomZoneLogger::log("Setting directory permissions", "INFO");
|
||||
if (!$this->setImageDirectoryPermissions()) {
|
||||
throw new Exception('Failed to set directory permissions');
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("Module installation completed successfully", "INFO");
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
// Log the error
|
||||
EcomZoneLogger::log('Module installation failed', 'ERROR', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
// Clean up any partial installation
|
||||
$this->uninstall();
|
||||
|
||||
// Set error message that can be retrieved
|
||||
$this->_errors[] = $this->l('Installation failed: ') . $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function createTables()
|
||||
{
|
||||
$sql = [];
|
||||
|
||||
// Add module logging table
|
||||
$sql[] = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'ecomzone_sync_log` (
|
||||
`id_sync_log` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`sync_date` datetime NOT NULL,
|
||||
`status` varchar(32) NOT NULL,
|
||||
`message` text,
|
||||
`details` text,
|
||||
PRIMARY KEY (`id_sync_log`)
|
||||
) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;';
|
||||
|
||||
// Add product sync table to track imported products
|
||||
$sql[] = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'ecomzone_product` (
|
||||
`id_product` int(11) NOT NULL,
|
||||
`reference` varchar(64) NOT NULL,
|
||||
`last_sync` datetime NOT NULL,
|
||||
`sync_status` varchar(32) NOT NULL,
|
||||
PRIMARY KEY (`id_product`),
|
||||
UNIQUE KEY `reference` (`reference`)
|
||||
) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=utf8;';
|
||||
|
||||
foreach ($sql as $query) {
|
||||
if (!Db::getInstance()->execute($query)) {
|
||||
throw new Exception('Failed to create required database table: ' . Db::getInstance()->getMsgError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function uninstall()
|
||||
{
|
||||
return parent::uninstall() &&
|
||||
Configuration::deleteByName("ECOMZONE_API_URL") &&
|
||||
Configuration::deleteByName("ECOMZONE_API_TOKEN") &&
|
||||
Configuration::deleteByName("ECOMZONE_CRON_TOKEN") &&
|
||||
Configuration::deleteByName("ECOMZONE_LAST_SYNC");
|
||||
EcomZoneLogger::log("Starting module uninstallation", "INFO");
|
||||
|
||||
try {
|
||||
// Remove configuration values
|
||||
$configKeys = [
|
||||
'ECOMZONE_API_URL',
|
||||
'ECOMZONE_API_TOKEN',
|
||||
'ECOMZONE_LAST_SYNC',
|
||||
'ECOMZONE_SYNC_STATE',
|
||||
'ECOMZONE_SYNC_PROGRESS',
|
||||
'ECOMZONE_LOG_LEVEL',
|
||||
'ECOMZONE_CRON_TOKEN'
|
||||
];
|
||||
|
||||
foreach ($configKeys as $key) {
|
||||
Configuration::deleteByName($key);
|
||||
}
|
||||
|
||||
// Uninstall parent module
|
||||
if (!parent::uninstall()) {
|
||||
throw new Exception('Failed to uninstall parent module');
|
||||
}
|
||||
|
||||
EcomZoneLogger::log("Module uninstallation completed successfully", "INFO");
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log('Module uninstallation failed', 'ERROR', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
$this->_errors[] = $this->l('Uninstallation failed: ') . $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
$output = "";
|
||||
// Ensure no output before AJAX responses
|
||||
if (Tools::getValue('ajax')) {
|
||||
ob_clean();
|
||||
header('Content-Type: application/json');
|
||||
}
|
||||
|
||||
$output = "";
|
||||
|
||||
// Get admin token
|
||||
$token = Tools::getAdminTokenLite('AdminModules');
|
||||
|
||||
if (Tools::isSubmit("submitEcomZoneModule")) {
|
||||
$apiToken = Tools::getValue("ECOMZONE_API_TOKEN");
|
||||
if (Configuration::updateValue("ECOMZONE_API_TOKEN", $apiToken)) {
|
||||
$output .= $this->displayConfirmation(
|
||||
$this->trans(
|
||||
"Settings updated",
|
||||
[],
|
||||
"Modules.Ecomzone.Admin"
|
||||
)
|
||||
);
|
||||
$apiUrl = Tools::getValue("ECOMZONE_API_URL");
|
||||
|
||||
Configuration::updateValue("ECOMZONE_API_TOKEN", $apiToken);
|
||||
Configuration::updateValue("ECOMZONE_API_URL", $apiUrl);
|
||||
|
||||
$output .= $this->displayConfirmation(
|
||||
$this->trans(
|
||||
"Settings updated",
|
||||
[],
|
||||
"Modules.Ecomzone.Admin"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Handle manual sync request
|
||||
if (Tools::isSubmit("syncProducts")) {
|
||||
try {
|
||||
// If this is an AJAX request, we need to handle it differently
|
||||
$isAjax = Tools::getValue('ajax');
|
||||
|
||||
if (empty(Configuration::get("ECOMZONE_API_TOKEN"))) {
|
||||
throw new Exception($this->l('API token not configured. Please configure the API token first.'));
|
||||
}
|
||||
|
||||
$productSync = new EcomZoneProductSync();
|
||||
|
||||
// Reset sync state when starting new sync
|
||||
if (!$isAjax) {
|
||||
$productSync->resetSyncState();
|
||||
}
|
||||
|
||||
$result = $productSync->importProducts(100);
|
||||
|
||||
if ($result['is_complete']) {
|
||||
Configuration::updateValue("ECOMZONE_LAST_SYNC", date("Y-m-d H:i:s"));
|
||||
}
|
||||
|
||||
// Store sync progress
|
||||
$progress = [
|
||||
'processed' => $result["imported"],
|
||||
'total' => $result["total"],
|
||||
'percentage' => ($result["total"] > 0) ? round(($result["imported"] / $result["total"]) * 100) : 0,
|
||||
'is_complete' => $result['is_complete'],
|
||||
'current_page' => $result['current_page']
|
||||
];
|
||||
|
||||
Configuration::updateValue("ECOMZONE_SYNC_PROGRESS", json_encode($progress));
|
||||
|
||||
if ($isAjax) {
|
||||
die(json_encode([
|
||||
'success' => true,
|
||||
'progress' => $progress
|
||||
]));
|
||||
}
|
||||
|
||||
if ($result['is_complete']) {
|
||||
$output .= $this->displayConfirmation(
|
||||
sprintf(
|
||||
$this->trans(
|
||||
"Products synchronized successfully. Imported: %d, Total Available: %d, Errors: %d",
|
||||
[],
|
||||
"Modules.Ecomzone.Admin"
|
||||
),
|
||||
$result["imported"],
|
||||
$result["total"],
|
||||
count($result["errors"])
|
||||
)
|
||||
);
|
||||
|
||||
if (!empty($result["errors"])) {
|
||||
$errorMessages = array_map(function($error) {
|
||||
return sprintf("SKU: %s - Error: %s", $error["sku"], $error["error"]);
|
||||
}, $result["errors"]);
|
||||
|
||||
$output .= $this->displayWarning(
|
||||
implode("<br>", $errorMessages)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Sync failed", "ERROR", [
|
||||
"error" => $e->getMessage(),
|
||||
"trace" => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
if ($isAjax) {
|
||||
die(json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]));
|
||||
}
|
||||
|
||||
$output .= $this->displayError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Handle product fetch request
|
||||
// Handle continue sync request (AJAX)
|
||||
if (Tools::getValue('action') === 'continueSyncProducts' && Tools::getValue('ajax')) {
|
||||
try {
|
||||
if (empty(Configuration::get("ECOMZONE_API_TOKEN"))) {
|
||||
throw new Exception($this->l('API token not configured. Please configure the API token first.'));
|
||||
}
|
||||
|
||||
$productSync = new EcomZoneProductSync();
|
||||
$result = $productSync->importProducts(100);
|
||||
|
||||
if ($result['is_complete']) {
|
||||
Configuration::updateValue("ECOMZONE_LAST_SYNC", date("Y-m-d H:i:s"));
|
||||
}
|
||||
|
||||
// Store sync progress
|
||||
$progress = [
|
||||
'processed' => $result["imported"],
|
||||
'total' => $result["total"],
|
||||
'percentage' => ($result["total"] > 0) ? round(($result["imported"] / $result["total"]) * 100) : 0,
|
||||
'is_complete' => $result['is_complete'],
|
||||
'current_page' => $result['current_page']
|
||||
];
|
||||
|
||||
Configuration::updateValue("ECOMZONE_SYNC_PROGRESS", json_encode($progress));
|
||||
|
||||
die(json_encode([
|
||||
'success' => true,
|
||||
'progress' => $progress
|
||||
]));
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Sync continuation failed", "ERROR", [
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
die(json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle product fetch request for preview
|
||||
if (Tools::isSubmit("fetchProducts") || Tools::getValue("page")) {
|
||||
try {
|
||||
$page = (int) Tools::getValue("page", 1);
|
||||
$perPage = 10;
|
||||
|
||||
$result = $this->makeApiRequest(
|
||||
"catalog?page={$page}&per_page={$perPage}"
|
||||
"catalog?page={$page}&per_page={$perPage}",
|
||||
"GET"
|
||||
);
|
||||
|
||||
if (!empty($result["data"])) {
|
||||
// Add sync status to products
|
||||
foreach ($result["data"] as &$product) {
|
||||
$productId = Product::getIdByReference($product["sku"]);
|
||||
$product["sync_status"] = $productId ? "success" : "pending";
|
||||
}
|
||||
|
||||
$totalPages = ceil($result["total"] / $perPage);
|
||||
|
||||
$this->context->smarty->assign([
|
||||
@ -131,15 +518,27 @@ class Ecomzone extends Module
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Product fetch failed", "ERROR", [
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
$output .= $this->displayError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get sync progress
|
||||
$syncProgress = json_decode(Configuration::get("ECOMZONE_SYNC_PROGRESS"), true);
|
||||
|
||||
// Get recent activity logs
|
||||
$activityLog = $this->getRecentActivityLogs();
|
||||
|
||||
$this->context->smarty->assign([
|
||||
"module_dir" => $this->_path,
|
||||
"ECOMZONE_API_TOKEN" => Configuration::get("ECOMZONE_API_TOKEN"),
|
||||
"ECOMZONE_CRON_TOKEN" => Configuration::get("ECOMZONE_CRON_TOKEN"),
|
||||
"ECOMZONE_API_URL" => Configuration::get("ECOMZONE_API_URL"),
|
||||
"ECOMZONE_LAST_SYNC" => Configuration::get("ECOMZONE_LAST_SYNC"),
|
||||
"SYNC_PROGRESS" => $syncProgress,
|
||||
"ACTIVITY_LOG" => $activityLog,
|
||||
"token" => $token,
|
||||
"current_url" =>
|
||||
$this->context->link->getAdminLink("AdminModules", false) .
|
||||
"&configure=" .
|
||||
@ -151,78 +550,77 @@ class Ecomzone extends Module
|
||||
"shop_url" => $this->context->link->getBaseLink(),
|
||||
]);
|
||||
|
||||
return $output .
|
||||
$this->display(__FILE__, "views/templates/admin/configure.tpl");
|
||||
return $output . $this->display(__FILE__, "views/templates/admin/configure.tpl");
|
||||
}
|
||||
|
||||
private function getRecentActivityLogs($limit = 50)
|
||||
{
|
||||
$logFile = _PS_MODULE_DIR_ . $this->name . '/log/ecomzone.log';
|
||||
$logs = [];
|
||||
|
||||
if (file_exists($logFile)) {
|
||||
$lines = array_reverse(file($logFile));
|
||||
$count = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ($count >= $limit) break;
|
||||
|
||||
$logEntry = json_decode($line, true);
|
||||
if ($logEntry) {
|
||||
$logs[] = [
|
||||
'timestamp' => $logEntry['timestamp'] ?? date('Y-m-d H:i:s'),
|
||||
'level' => $logEntry['level'] ?? 'INFO',
|
||||
'message' => $logEntry['message'] ?? '',
|
||||
'details' => json_encode($logEntry['context'] ?? [])
|
||||
];
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
private function makeApiRequest($endpoint, $method = "GET", $data = null)
|
||||
{
|
||||
$apiUrl = Configuration::get("ECOMZONE_API_URL");
|
||||
$apiToken = Configuration::get("ECOMZONE_API_TOKEN");
|
||||
|
||||
if (empty($apiToken)) {
|
||||
throw new Exception(
|
||||
$this->trans(
|
||||
"API token not configured",
|
||||
[],
|
||||
"Modules.Ecomzone.Admin"
|
||||
)
|
||||
);
|
||||
try {
|
||||
// Use the EcomZoneClient for all API requests
|
||||
$client = new EcomZoneClient();
|
||||
|
||||
// Add endpoint to URL
|
||||
$params = [];
|
||||
|
||||
// Extract query params if present in endpoint
|
||||
if (strpos($endpoint, '?') !== false) {
|
||||
list($endpoint, $queryString) = explode('?', $endpoint, 2);
|
||||
parse_str($queryString, $params);
|
||||
}
|
||||
|
||||
// Ensure method is a string and not an array (prevent 405 Method Not Allowed)
|
||||
if (!is_string($method)) {
|
||||
EcomZoneLogger::log("Invalid method type detected", "WARNING", [
|
||||
'endpoint' => $endpoint,
|
||||
'method_type' => gettype($method),
|
||||
'method' => $method
|
||||
]);
|
||||
$method = "GET"; // Default to GET if method is not a string
|
||||
}
|
||||
|
||||
// Make the request using our client
|
||||
if ($method === 'GET') {
|
||||
return $client->makeRequest($endpoint, $method, $params);
|
||||
} else {
|
||||
return $client->makeRequest($endpoint, $method, [], $data);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("API Request Failed in module", "ERROR", [
|
||||
'endpoint' => $endpoint,
|
||||
'method' => $method,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
$url = rtrim($apiUrl, "/") . "/" . ltrim($endpoint, "/");
|
||||
|
||||
$options = [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer " . $apiToken,
|
||||
"Accept: application/json",
|
||||
"Content-Type: application/json",
|
||||
],
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
];
|
||||
|
||||
if ($data !== null) {
|
||||
$options[CURLOPT_POSTFIELDS] = json_encode($data);
|
||||
}
|
||||
|
||||
curl_setopt_array($curl, $options);
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($err) {
|
||||
throw new Exception("cURL Error: " . $err);
|
||||
}
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
$errorData = json_decode($response, true);
|
||||
$errorMessage = isset($errorData['error'])
|
||||
? $errorData['error']
|
||||
: "HTTP Error: " . $httpCode;
|
||||
throw new EcomZoneException(
|
||||
$errorMessage,
|
||||
EcomZoneException::API_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
$decodedResponse = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("Invalid JSON response");
|
||||
}
|
||||
|
||||
return $decodedResponse;
|
||||
}
|
||||
|
||||
public function hookActionCronJob($params)
|
||||
@ -245,7 +643,8 @@ class Ecomzone extends Module
|
||||
|
||||
do {
|
||||
$result = $this->makeApiRequest(
|
||||
"catalog?page={$page}&per_page={$perPage}"
|
||||
"catalog?page={$page}&per_page={$perPage}",
|
||||
"GET"
|
||||
);
|
||||
|
||||
if (empty($result["data"])) {
|
||||
@ -341,5 +740,35 @@ class Ecomzone extends Module
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function hookActionProductUpdate($params)
|
||||
{
|
||||
try {
|
||||
$product = $params['product'];
|
||||
EcomZoneLogger::log("Product updated", "INFO", [
|
||||
"product_id" => $product->id,
|
||||
"reference" => $product->reference
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to handle product update", "ERROR", [
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function hookActionProductDelete($params)
|
||||
{
|
||||
try {
|
||||
$product = $params['product'];
|
||||
EcomZoneLogger::log("Product deleted", "INFO", [
|
||||
"product_id" => $product->id,
|
||||
"reference" => $product->reference
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Failed to handle product deletion", "ERROR", [
|
||||
"error" => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
53
ecomzone/get_token.php
Normal file
53
ecomzone/get_token.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
// Simple script to get the API token from the PrestaShop configuration
|
||||
|
||||
// Check if we're running from command line
|
||||
$cli = php_sapi_name() === 'cli';
|
||||
|
||||
// Set content type for browser
|
||||
if (!$cli) {
|
||||
header('Content-Type: text/plain');
|
||||
}
|
||||
|
||||
// Try to load PrestaShop configuration
|
||||
$configFile = __DIR__ . '/../../config/config.inc.php';
|
||||
if (file_exists($configFile)) {
|
||||
include_once($configFile);
|
||||
|
||||
// If Configuration class exists, try to get token
|
||||
if (class_exists('Configuration')) {
|
||||
$token = Configuration::get('ECOMZONE_API_TOKEN');
|
||||
echo "API Token from configuration: " . ($token ? $token : "Not set") . "\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// If PrestaShop config doesn't work, try direct DB connection using default credentials
|
||||
$dbConfig = [
|
||||
'host' => 'localhost',
|
||||
'username' => 'prestashop',
|
||||
'password' => 'prestashop',
|
||||
'database' => 'prestashop',
|
||||
'prefix' => 'ps_'
|
||||
];
|
||||
|
||||
echo "Attempting direct database connection...\n";
|
||||
|
||||
try {
|
||||
$db = new PDO("mysql:host={$dbConfig['host']};dbname={$dbConfig['database']}", $dbConfig['username'], $dbConfig['password']);
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$stmt = $db->prepare("SELECT value FROM {$dbConfig['prefix']}configuration WHERE name = 'ECOMZONE_API_TOKEN'");
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result && isset($result['value'])) {
|
||||
echo "API Token found in database: " . $result['value'] . "\n";
|
||||
} else {
|
||||
echo "API Token not found in database.\n";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
echo "Database connection failed: " . $e->getMessage() . "\n";
|
||||
echo "You'll need to manually update the api_test.php file with your API token.\n";
|
||||
}
|
||||
1
ecomzone/readme_api_fix.txt
Normal file
1
ecomzone/readme_api_fix.txt
Normal file
@ -0,0 +1 @@
|
||||
|
||||
174
ecomzone/test_api.php
Normal file
174
ecomzone/test_api.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
// Script to test API connectivity
|
||||
|
||||
// Display errors
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Define required constants if not defined
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
define('_PS_VERSION_', '8.0.0');
|
||||
}
|
||||
if (!defined('_PS_MODULE_DIR_')) {
|
||||
define('_PS_MODULE_DIR_', __DIR__ . '/../../modules/');
|
||||
}
|
||||
if (!defined('_PS_ROOT_DIR_')) {
|
||||
define('_PS_ROOT_DIR_', dirname(__DIR__, 2));
|
||||
}
|
||||
|
||||
// Load required classes directly
|
||||
require_once(dirname(__FILE__) . '/classes/EcomZoneLogger.php');
|
||||
require_once(dirname(__FILE__) . '/classes/EcomZoneException.php');
|
||||
require_once(dirname(__FILE__) . '/classes/EcomZoneClient.php');
|
||||
|
||||
// Create log directory if it doesn't exist
|
||||
$logDir = __DIR__ . '/log';
|
||||
if (!file_exists($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
// Mock PrestaShop classes
|
||||
if (!class_exists('Configuration')) {
|
||||
class Configuration {
|
||||
public static function get($key, $default = null) {
|
||||
$config = [
|
||||
'ECOMZONE_API_URL' => 'https://dropship.ecomzone.eu/api',
|
||||
'ECOMZONE_API_TOKEN' => 'klRyAdrXaxL0s6PEUp7LDlH6T8aPSCtBY8NiEHsHiWpc6646K2TZPi5KMxUg' // Actual API token
|
||||
];
|
||||
return isset($config[$key]) ? $config[$key] : $default;
|
||||
}
|
||||
|
||||
public static function updateValue($key, $value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('Context')) {
|
||||
class Context {
|
||||
public $shop;
|
||||
public static $instance;
|
||||
|
||||
public function __construct() {
|
||||
$this->shop = new stdClass();
|
||||
$this->shop->id = 1;
|
||||
}
|
||||
|
||||
public static function getContext() {
|
||||
if (!self::$instance) {
|
||||
self::$instance = new Context();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('PrestaShopLogger')) {
|
||||
class PrestaShopLogger {
|
||||
public static function addLog($message, $severity = 1, $error_code = null, $object_type = null, $object_id = null, $allow_duplicate = false, $id_employee = null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable output buffering
|
||||
if (ob_get_level()) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Set headers for plain text output
|
||||
header('Content-Type: text/plain');
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||
|
||||
echo "EcomZone API Test\n";
|
||||
echo "================\n\n";
|
||||
|
||||
try {
|
||||
// Get API settings
|
||||
$apiUrl = Configuration::get('ECOMZONE_API_URL');
|
||||
$apiToken = Configuration::get('ECOMZONE_API_TOKEN');
|
||||
|
||||
echo "API URL: " . $apiUrl . "\n";
|
||||
echo "API Token: " . (empty($apiToken) || $apiToken === 'YOUR_API_TOKEN' ?
|
||||
"Not set - Please edit test_api.php and replace YOUR_API_TOKEN with your actual token" :
|
||||
"Set (length: " . strlen($apiToken) . " chars)") . "\n\n";
|
||||
|
||||
if (empty($apiToken) || $apiToken === 'YOUR_API_TOKEN') {
|
||||
throw new Exception("API token is not configured. Please edit test_api.php and replace YOUR_API_TOKEN with your actual token.");
|
||||
}
|
||||
|
||||
echo "Testing API connection...\n\n";
|
||||
|
||||
// Create API client
|
||||
$client = new EcomZoneClient();
|
||||
|
||||
// Test API connectivity with catalog endpoint
|
||||
echo "Requesting catalog data (page 1, limit 1)...\n";
|
||||
$start = microtime(true);
|
||||
$result = $client->getCatalog(1, 1);
|
||||
$end = microtime(true);
|
||||
|
||||
echo "Request completed in " . round(($end - $start) * 1000, 2) . " ms\n";
|
||||
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
echo "Success! Received " . count($result['data']) . " product(s)\n";
|
||||
echo "Total products available: " . ($result['total'] ?? 'unknown') . "\n\n";
|
||||
|
||||
if (!empty($result['data'])) {
|
||||
$sku = $result['data'][0]['sku'] ?? null;
|
||||
|
||||
if ($sku) {
|
||||
echo "Testing product detail retrieval...\n";
|
||||
echo "Requesting product with SKU: " . $sku . "\n";
|
||||
|
||||
$start = microtime(true);
|
||||
$productDetail = $client->getProduct($sku);
|
||||
$end = microtime(true);
|
||||
|
||||
echo "Request completed in " . round(($end - $start) * 1000, 2) . " ms\n";
|
||||
|
||||
if (isset($productDetail['data']) && !empty($productDetail['data'])) {
|
||||
echo "Success! Retrieved product details\n";
|
||||
echo "Product name: " . ($productDetail['data']['product_name'] ?? 'unknown') . "\n";
|
||||
} else {
|
||||
echo "Error: Failed to retrieve product details\n";
|
||||
echo "Response: " . print_r($productDetail, true) . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "Error: Invalid catalog response\n";
|
||||
echo "Response: " . print_r($result, true) . "\n";
|
||||
}
|
||||
|
||||
echo "\nAPI test completed successfully!\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "ERROR: " . $e->getMessage() . "\n";
|
||||
|
||||
if ($e instanceof EcomZoneException) {
|
||||
echo "Error Code: " . $e->getCode() . "\n";
|
||||
}
|
||||
|
||||
echo "\nStack Trace:\n" . $e->getTraceAsString() . "\n";
|
||||
|
||||
echo "\nCheck the log file for more details: modules/ecomzone/log/ecomzone.log\n";
|
||||
}
|
||||
|
||||
echo "\nLog contents (last 10 entries):\n";
|
||||
echo "==============================\n\n";
|
||||
|
||||
try {
|
||||
$logEntries = EcomZoneLogger::getLogContents(10);
|
||||
foreach ($logEntries as $entry) {
|
||||
$context = '';
|
||||
if (!empty($entry['context'])) {
|
||||
$context = ' - ' . json_encode($entry['context']);
|
||||
}
|
||||
echo $entry['timestamp'] . ' [' . $entry['level'] . '] ' . $entry['message'] . $context . "\n";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "Failed to retrieve logs: " . $e->getMessage() . "\n";
|
||||
}
|
||||
194
ecomzone/test_image_download.php
Normal file
194
ecomzone/test_image_download.php
Normal file
@ -0,0 +1,194 @@
|
||||
<?php
|
||||
// Test script for image downloading capabilities
|
||||
|
||||
// Display errors
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Create necessary directories
|
||||
$directories = [
|
||||
'tmp',
|
||||
'images',
|
||||
'images/products',
|
||||
'log'
|
||||
];
|
||||
|
||||
echo "Setting up test directories...\n";
|
||||
foreach ($directories as $dir) {
|
||||
if (!file_exists($dir)) {
|
||||
if (mkdir($dir, 0777, true)) {
|
||||
echo "Created directory: $dir\n";
|
||||
} else {
|
||||
echo "Failed to create directory: $dir\n";
|
||||
}
|
||||
} else {
|
||||
echo "Directory exists: $dir\n";
|
||||
}
|
||||
chmod($dir, 0777);
|
||||
}
|
||||
|
||||
// Define test image URL
|
||||
$testImageUrl = 'https://dropship.ecomzone.eu/storage/products/2023/10/05/2442_911_01_Xmas_bag_2_6BFQAO9yv8.jpg';
|
||||
|
||||
echo "\nTesting image download...\n";
|
||||
echo "Image URL: $testImageUrl\n\n";
|
||||
|
||||
// Method 1: Using file_get_contents
|
||||
echo "Method 1: Using file_get_contents\n";
|
||||
try {
|
||||
$tempFile = 'tmp/test_image_1.jpg';
|
||||
$imageData = @file_get_contents($testImageUrl);
|
||||
|
||||
if ($imageData === false) {
|
||||
echo "FAILED: Could not download image using file_get_contents\n";
|
||||
echo "Error: " . error_get_last()['message'] . "\n";
|
||||
} else {
|
||||
file_put_contents($tempFile, $imageData);
|
||||
$fileSize = filesize($tempFile);
|
||||
echo "SUCCESS: Downloaded " . number_format($fileSize) . " bytes to $tempFile\n";
|
||||
|
||||
// Check if it's a valid image
|
||||
$imageInfo = getimagesize($tempFile);
|
||||
if ($imageInfo) {
|
||||
echo "Image dimensions: " . $imageInfo[0] . 'x' . $imageInfo[1] . ", type: " . $imageInfo['mime'] . "\n";
|
||||
} else {
|
||||
echo "WARNING: The downloaded file is not a valid image\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "FAILED: Exception occurred: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Method 2: Using cURL with detailed debugging
|
||||
echo "Using cURL with detailed debugging\n";
|
||||
try {
|
||||
$tempFile = 'tmp/test_image_debug.jpg';
|
||||
|
||||
if (!function_exists('curl_init')) {
|
||||
echo "FAILED: cURL is not installed\n";
|
||||
} else {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $testImageUrl,
|
||||
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_HEADER => 1, // Include headers in the response
|
||||
CURLOPT_VERBOSE => true,
|
||||
]);
|
||||
|
||||
// Create a stream for the verbose output
|
||||
$verbose = fopen('tmp/curl_verbose.log', 'w+');
|
||||
curl_setopt($ch, CURLOPT_STDERR, $verbose);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$error = curl_error($ch);
|
||||
|
||||
// Extract headers and body
|
||||
$headers = substr($response, 0, $headerSize);
|
||||
$imageData = substr($response, $headerSize);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
// Close and read the verbose log
|
||||
rewind($verbose);
|
||||
$verboseLog = stream_get_contents($verbose);
|
||||
fclose($verbose);
|
||||
|
||||
echo "HTTP Status Code: $httpCode\n";
|
||||
echo "Content Type: $contentType\n";
|
||||
echo "Headers:\n$headers\n";
|
||||
|
||||
if ($httpCode !== 200 || empty($imageData)) {
|
||||
echo "FAILED: HTTP Error $httpCode - $error\n";
|
||||
} else {
|
||||
file_put_contents($tempFile, $imageData);
|
||||
$fileSize = filesize($tempFile);
|
||||
echo "Downloaded " . number_format($fileSize) . " bytes to $tempFile\n";
|
||||
|
||||
// Check if it's a valid image
|
||||
$imageInfo = @getimagesize($tempFile);
|
||||
if ($imageInfo) {
|
||||
echo "SUCCESS: Valid image detected\n";
|
||||
echo "Image dimensions: " . $imageInfo[0] . 'x' . $imageInfo[1] . ", type: " . $imageInfo['mime'] . "\n";
|
||||
} else {
|
||||
echo "WARNING: The downloaded file is not a valid image\n";
|
||||
|
||||
// Analyze the first 100 bytes of the content
|
||||
echo "First 100 bytes of the content:\n";
|
||||
$contentPreview = bin2hex(substr($imageData, 0, 50));
|
||||
echo chunk_split($contentPreview, 2, ' ') . "\n";
|
||||
|
||||
// Show first 100 characters if it looks like text
|
||||
echo "Content as text (first 100 chars):\n";
|
||||
$textPreview = substr($imageData, 0, 100);
|
||||
echo preg_replace('/[^\x20-\x7E]/', '.', $textPreview) . "\n";
|
||||
}
|
||||
|
||||
echo "\nCURL Verbose Log:\n$verboseLog\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "FAILED: Exception occurred: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
// Try a publicly accessible image as a reference test
|
||||
echo "\n\nTesting with a public reference image...\n";
|
||||
$publicImageUrl = 'https://www.php.net/images/logos/new-php-logo.svg';
|
||||
echo "Public Image URL: $publicImageUrl\n\n";
|
||||
|
||||
try {
|
||||
$tempFile = 'tmp/test_reference_image.jpg';
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $publicImageUrl,
|
||||
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',
|
||||
]);
|
||||
|
||||
$imageData = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP Status Code: $httpCode\n";
|
||||
echo "Content Type: $contentType\n";
|
||||
|
||||
if ($httpCode !== 200 || empty($imageData)) {
|
||||
echo "FAILED: Could not download reference image\n";
|
||||
} else {
|
||||
file_put_contents($tempFile, $imageData);
|
||||
$fileSize = filesize($tempFile);
|
||||
echo "Downloaded " . number_format($fileSize) . " bytes to $tempFile\n";
|
||||
|
||||
// Check if it's a valid image
|
||||
$imageInfo = @getimagesize($tempFile);
|
||||
if ($imageInfo) {
|
||||
echo "SUCCESS: Valid image detected\n";
|
||||
echo "Image dimensions: " . $imageInfo[0] . 'x' . $imageInfo[1] . ", type: " . $imageInfo['mime'] . "\n";
|
||||
} else {
|
||||
echo "WARNING: The downloaded file is not a valid image\n";
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "FAILED: Exception occurred: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\nTest completed\n";
|
||||
318
ecomzone/test_image_with_auth.php
Normal file
318
ecomzone/test_image_with_auth.php
Normal file
@ -0,0 +1,318 @@
|
||||
<?php
|
||||
// Test script for downloading images with API token authentication
|
||||
|
||||
// Display errors
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Create necessary directories
|
||||
$directories = [
|
||||
'tmp',
|
||||
'images',
|
||||
'images/products',
|
||||
'log'
|
||||
];
|
||||
|
||||
echo "Setting up test directories...\n";
|
||||
foreach ($directories as $dir) {
|
||||
if (!file_exists($dir)) {
|
||||
if (mkdir($dir, 0777, true)) {
|
||||
echo "Created directory: $dir\n";
|
||||
} else {
|
||||
echo "Failed to create directory: $dir\n";
|
||||
}
|
||||
} else {
|
||||
echo "Directory exists: $dir\n";
|
||||
}
|
||||
chmod($dir, 0777);
|
||||
}
|
||||
|
||||
// Prompt for API token if not set
|
||||
if (!isset($argv[1]) || empty($argv[1])) {
|
||||
echo "Please enter your EcomZone API token: ";
|
||||
$apiToken = trim(fgets(STDIN));
|
||||
} else {
|
||||
$apiToken = $argv[1];
|
||||
}
|
||||
|
||||
echo "\nTesting with API token: " . substr($apiToken, 0, 5) . '...' . substr($apiToken, -5) . "\n\n";
|
||||
|
||||
// Function to make API requests
|
||||
function makeApiRequest($endpoint, $apiToken) {
|
||||
$apiUrl = 'https://dropship.ecomzone.eu/api/' . ltrim($endpoint, '/');
|
||||
|
||||
echo "Requesting: $apiUrl\n";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $apiUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $apiToken
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
$errorMsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP Status Code: $httpCode\n";
|
||||
echo "Content Type: $contentType\n";
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
echo "API Error: $errorMsg\n";
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
echo "JSON Decode Error: " . json_last_error_msg() . "\n";
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
// Use the specific SKU provided by the user
|
||||
$sku = '1817-911';
|
||||
echo "Using the specific SKU: $sku\n\n";
|
||||
|
||||
// Skip catalog fetching step since we have a specific SKU to test
|
||||
|
||||
// STEP 2: Get detailed product information
|
||||
echo "STEP 2: Fetching detailed product information for SKU: $sku\n";
|
||||
$productData = makeApiRequest('product/' . urlencode($sku), $apiToken);
|
||||
|
||||
if (!$productData || empty($productData['data'])) {
|
||||
echo "Failed to get product details.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Extract image URLs from product data
|
||||
$imageUrls = [];
|
||||
|
||||
if (!empty($productData['data']['image'])) {
|
||||
$imageUrls[] = $productData['data']['image'];
|
||||
echo "Found main image: " . $productData['data']['image'] . "\n";
|
||||
}
|
||||
|
||||
// Try to parse images field which might be JSON string or array
|
||||
if (!empty($productData['data']['images'])) {
|
||||
if (is_string($productData['data']['images'])) {
|
||||
$parsedImages = json_decode($productData['data']['images'], true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($parsedImages)) {
|
||||
foreach ($parsedImages as $img) {
|
||||
$imageUrls[] = $img;
|
||||
echo "Found additional image (from JSON string): $img\n";
|
||||
}
|
||||
}
|
||||
} elseif (is_array($productData['data']['images'])) {
|
||||
foreach ($productData['data']['images'] as $img) {
|
||||
$imageUrls[] = $img;
|
||||
echo "Found additional image (from array): $img\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($imageUrls)) {
|
||||
echo "No image URLs found in product data.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// STEP 3: Try direct download of the first image with authorization
|
||||
echo "\nSTEP 3: Attempting to download the first image directly with Authorization header\n";
|
||||
$firstImageUrl = $imageUrls[0];
|
||||
echo "Image URL: $firstImageUrl\n";
|
||||
|
||||
// Try with direct download
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $firstImageUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: image/webp,image/apng,image/*,*/*;q=0.8',
|
||||
'Authorization: Bearer ' . $apiToken
|
||||
]
|
||||
]);
|
||||
|
||||
$imageData = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP Status Code: $httpCode\n";
|
||||
echo "Content Type: $contentType\n";
|
||||
|
||||
// Check if the response is HTML (authentication error)
|
||||
$isHtml = stripos($contentType, 'text/html') !== false ||
|
||||
(strlen($imageData) > 15 && stripos($imageData, '<!DOCTYPE html>') !== false);
|
||||
|
||||
if ($isHtml) {
|
||||
echo "FAILED: Authentication error. Received HTML instead of image data.\n";
|
||||
file_put_contents('tmp/auth_error.html', $imageData);
|
||||
echo "Full HTML response saved to tmp/auth_error.html\n";
|
||||
} else {
|
||||
$tempFile = 'tmp/direct_download.jpg';
|
||||
file_put_contents($tempFile, $imageData);
|
||||
$fileSize = filesize($tempFile);
|
||||
echo "Downloaded " . number_format($fileSize) . " bytes to $tempFile\n";
|
||||
|
||||
$imageInfo = @getimagesize($tempFile);
|
||||
if ($imageInfo) {
|
||||
echo "SUCCESS: Valid image detected\n";
|
||||
echo "Image dimensions: " . $imageInfo[0] . 'x' . $imageInfo[1] . ", type: " . $imageInfo['mime'] . "\n";
|
||||
} else {
|
||||
echo "WARNING: The downloaded file is not a valid image\n";
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 4: Check if there's a dedicated API endpoint for downloading images
|
||||
echo "\nSTEP 4: Checking if there's a dedicated image download API endpoint\n";
|
||||
|
||||
// Extract image ID from the URL (last part after the slash)
|
||||
$urlParts = explode('/', $firstImageUrl);
|
||||
$imageId = end($urlParts);
|
||||
|
||||
echo "Trying to access image via API using ID: $imageId\n";
|
||||
|
||||
// Try these possible API endpoints for image download
|
||||
$possibleEndpoints = [
|
||||
'image/' . $imageId,
|
||||
'download/image/' . $imageId,
|
||||
'media/' . $imageId,
|
||||
'product/image/' . $imageId,
|
||||
'product/' . $sku . '/image/' . $imageId
|
||||
];
|
||||
|
||||
$imageDownloaded = false;
|
||||
foreach ($possibleEndpoints as $endpoint) {
|
||||
echo "\nTrying endpoint: $endpoint\n";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => 'https://dropship.ecomzone.eu/api/' . $endpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: image/webp,image/apng,image/*,*/*;q=0.8',
|
||||
'Authorization: Bearer ' . $apiToken
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP Status Code: $httpCode\n";
|
||||
echo "Content Type: $contentType\n";
|
||||
|
||||
if ($httpCode === 200 && !stripos($contentType, 'text/html')) {
|
||||
$tempFile = 'tmp/api_' . basename($endpoint) . '.jpg';
|
||||
file_put_contents($tempFile, $response);
|
||||
|
||||
$imageInfo = @getimagesize($tempFile);
|
||||
if ($imageInfo) {
|
||||
echo "SUCCESS: Valid image downloaded via API endpoint\n";
|
||||
echo "Image dimensions: " . $imageInfo[0] . 'x' . $imageInfo[1] . ", type: " . $imageInfo['mime'] . "\n";
|
||||
$imageDownloaded = true;
|
||||
} else {
|
||||
echo "Response doesn't appear to be a valid image\n";
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$imageDownloaded) {
|
||||
echo "\nNone of the attempted API endpoints worked for direct image download.\n";
|
||||
}
|
||||
|
||||
// STEP 5: Try using an alternative base URL for the image
|
||||
echo "\nSTEP 5: Trying alternative base URLs for the image\n";
|
||||
|
||||
// Extract the path part from the URL
|
||||
$parsedUrl = parse_url($firstImageUrl);
|
||||
$imagePath = $parsedUrl['path'] ?? '';
|
||||
|
||||
if (empty($imagePath)) {
|
||||
echo "Could not extract image path from URL.\n";
|
||||
} else {
|
||||
echo "Image path: $imagePath\n";
|
||||
|
||||
// Try with alternative base URLs
|
||||
$alternativeBaseUrls = [
|
||||
'https://api.dropship.ecomzone.eu',
|
||||
'https://cdn.dropship.ecomzone.eu',
|
||||
'https://media.dropship.ecomzone.eu',
|
||||
'https://images.dropship.ecomzone.eu'
|
||||
];
|
||||
|
||||
foreach ($alternativeBaseUrls as $baseUrl) {
|
||||
$fullUrl = $baseUrl . $imagePath;
|
||||
echo "\nTrying alternative URL: $fullUrl\n";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $fullUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: image/webp,image/apng,image/*,*/*;q=0.8',
|
||||
'Authorization: Bearer ' . $apiToken
|
||||
]
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($ch);
|
||||
|
||||
echo "HTTP Status Code: $httpCode\n";
|
||||
echo "Content Type: $contentType\n";
|
||||
|
||||
if ($httpCode === 200 && !stripos($contentType, 'text/html')) {
|
||||
$tempFile = 'tmp/alt_domain_' . str_replace(['https://', '.'], ['', '_'], $baseUrl) . '.jpg';
|
||||
file_put_contents($tempFile, $response);
|
||||
|
||||
$imageInfo = @getimagesize($tempFile);
|
||||
if ($imageInfo) {
|
||||
echo "SUCCESS: Valid image downloaded from alternative URL\n";
|
||||
echo "Image dimensions: " . $imageInfo[0] . 'x' . $imageInfo[1] . ", type: " . $imageInfo['mime'] . "\n";
|
||||
} else {
|
||||
echo "Response doesn't appear to be a valid image\n";
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
echo "\n=== SUMMARY ===\n";
|
||||
echo "Product SKU: $sku\n";
|
||||
echo "Found " . count($imageUrls) . " image URLs in product data\n";
|
||||
echo "Direct image downloads with Authorization header don't work - server returns HTML login page\n";
|
||||
|
||||
echo "\nPOSSIBLE SOLUTIONS:\n";
|
||||
echo "1. The EcomZone API doesn't support direct image downloads through Authorization headers\n";
|
||||
echo "2. We may need to use a different authentication method for images\n";
|
||||
echo "3. Images might require a signed URL or temporary access token\n";
|
||||
echo "4. Consider contacting EcomZone support for the correct way to download images\n";
|
||||
echo "\nTest completed\n";
|
||||
@ -1,215 +1,357 @@
|
||||
|
||||
{* views/templates/admin/configure.tpl *}
|
||||
{*
|
||||
* @author EcomZone
|
||||
* @copyright EcomZone
|
||||
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
|
||||
*}
|
||||
|
||||
<div class="panel">
|
||||
<h3><i class="icon icon-cogs"></i> {l s='EcomZone Configuration' mod='ecomzone'}</h3>
|
||||
<form method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='API Token' mod='ecomzone'}</label>
|
||||
<div class="col-lg-6">
|
||||
<input type="text" name="ECOMZONE_API_TOKEN"
|
||||
value="{$ECOMZONE_API_TOKEN|escape:'html':'UTF-8'}"
|
||||
class="form-control" />
|
||||
|
||||
<form id="configuration_form" class="defaultForm form-horizontal" action="{$current_url}" method="post">
|
||||
<input type="hidden" name="token" value="{$token}" />
|
||||
<div class="panel">
|
||||
<div class="form-wrapper">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='API URL' mod='ecomzone'}</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" name="ECOMZONE_API_URL" value="{$ECOMZONE_API_URL|escape:'html':'UTF-8'}" class="form-control" required="required" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='API Token' mod='ecomzone'}</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" name="ECOMZONE_API_TOKEN" value="{$ECOMZONE_API_TOKEN|escape:'html':'UTF-8'}" class="form-control" required="required" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer">
|
||||
<button type="submit" name="submitEcomZoneModule" class="btn btn-default pull-right">
|
||||
<i class="process-icon-save"></i> {l s='Save' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="panel">
|
||||
<h3>{l s='Product Synchronization' mod='ecomzone'}</h3>
|
||||
<div class="form-wrapper">
|
||||
<p>{l s='Last synchronization:' mod='ecomzone'} <strong>{if $ECOMZONE_LAST_SYNC}{$ECOMZONE_LAST_SYNC}{else}{l s='Never' mod='ecomzone'}{/if}</strong></p>
|
||||
|
||||
<form action="{$current_url}" method="post" class="form-horizontal" id="sync-form">
|
||||
<input type="hidden" name="token" value="{$token}" />
|
||||
<div class="form-group">
|
||||
<div class="col-lg-9 col-lg-offset-3">
|
||||
<button type="submit" name="syncProducts" class="btn btn-primary" id="sync-button">
|
||||
<i class="process-icon-refresh"></i> {l s='Synchronize Products Now' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='Last Sync' mod='ecomzone'}</label>
|
||||
<div class="col-lg-6">
|
||||
<p class="form-control-static">
|
||||
{if $ECOMZONE_LAST_SYNC}
|
||||
{$ECOMZONE_LAST_SYNC|escape:'html':'UTF-8'}
|
||||
{else}
|
||||
{l s='Never' mod='ecomzone'}
|
||||
{/if}
|
||||
<div id="sync-progress" style="display: none;">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%;">
|
||||
0%
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<span id="sync-status">{l s='Processing...' mod='ecomzone'}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-footer">
|
||||
<button type="submit" name="submitEcomZoneModule" class="btn btn-default pull-right">
|
||||
<i class="process-icon-save"></i> {l s='Save' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h3>{l s='Product Preview' mod='ecomzone'}</h3>
|
||||
<div class="form-wrapper">
|
||||
<form action="{$current_url}" method="post" class="form-horizontal">
|
||||
<input type="hidden" name="token" value="{$token}" />
|
||||
<div class="form-group">
|
||||
<div class="col-lg-9 col-lg-offset-3">
|
||||
<button type="submit" name="fetchProducts" class="btn btn-default">
|
||||
<i class="process-icon-preview"></i> {l s='Preview Available Products' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{if isset($API_PRODUCTS) && $API_PRODUCTS}
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{l s='SKU' mod='ecomzone'}</th>
|
||||
<th>{l s='Name' mod='ecomzone'}</th>
|
||||
<th>{l s='Price' mod='ecomzone'}</th>
|
||||
<th>{l s='Stock' mod='ecomzone'}</th>
|
||||
<th>{l s='Status' mod='ecomzone'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach from=$API_PRODUCTS item=product}
|
||||
<tr>
|
||||
<td>{$product.sku|escape:'html':'UTF-8'}</td>
|
||||
<td>{$product.product_name|escape:'html':'UTF-8'}</td>
|
||||
<td>{$product.product_price|escape:'html':'UTF-8'}</td>
|
||||
<td>{$product.stock|escape:'html':'UTF-8'}</td>
|
||||
<td>
|
||||
{if isset($product.sync_status)}
|
||||
<span class="badge badge-{if $product.sync_status == 'success'}success{elseif $product.sync_status == 'error'}danger{else}info{/if}">
|
||||
{$product.sync_status|escape:'html':'UTF-8'}
|
||||
</span>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{if isset($PAGINATION)}
|
||||
<div class="text-center">
|
||||
<ul class="pagination">
|
||||
{for $page=1 to $PAGINATION.total_pages}
|
||||
<li {if $page == $PAGINATION.current_page}class="active"{/if}>
|
||||
<a href="{$current_url}&token={$token}&page={$page}">{$page}</a>
|
||||
</li>
|
||||
{/for}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if isset($SYNC_PROGRESS)}
|
||||
<div class="panel">
|
||||
<h3><i class="icon icon-cloud-download"></i> {l s='Product Import' mod='ecomzone'}</h3>
|
||||
<h3><i class="icon icon-refresh"></i> {l s='Sync Progress' mod='ecomzone'}</h3>
|
||||
<div class="panel-body">
|
||||
<form method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-lg-9 col-lg-offset-3">
|
||||
<button type="submit" name="fetchProducts" class="btn btn-primary">
|
||||
<i class="process-icon-download"></i> {l s='Fetch Products' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: {$SYNC_PROGRESS.percentage}%;">
|
||||
{$SYNC_PROGRESS.percentage}%
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p class="text-center">
|
||||
{l s='Processed:' mod='ecomzone'} {$SYNC_PROGRESS.processed} / {$SYNC_PROGRESS.total}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{if isset($API_PRODUCTS) && $API_PRODUCTS}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{l s='SKU' mod='ecomzone'}</th>
|
||||
<th>{l s='Name' mod='ecomzone'}</th>
|
||||
<th>{l s='Price' mod='ecomzone'}</th>
|
||||
<th>{l s='Stock' mod='ecomzone'}</th>
|
||||
<th class="text-right">{l s='Actions' mod='ecomzone'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{foreach from=$API_PRODUCTS item=product}
|
||||
<tr>
|
||||
<td>{$product.sku|escape:'html':'UTF-8'}</td>
|
||||
<td>{$product.product_name|escape:'html':'UTF-8'}</td>
|
||||
<td>{$product.product_price|escape:'html':'UTF-8'}</td>
|
||||
<td>{$product.stock|escape:'html':'UTF-8'}</td>
|
||||
<td class="text-right">
|
||||
<form method="post" style="display:inline;">
|
||||
<input type="hidden" name="import_sku"
|
||||
value="{$product.sku|escape:'html':'UTF-8'}" />
|
||||
<button type="submit" name="importSingleProduct"
|
||||
class="btn btn-xs btn-primary"
|
||||
title="{l s='Import this product' mod='ecomzone'}">
|
||||
<i class="icon-download"></i> {l s='Import' mod='ecomzone'}
|
||||
<div class="panel">
|
||||
<h3><i class="icon icon-list"></i> {l s='Recent Activity Log' mod='ecomzone'}</h3>
|
||||
<div class="panel-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{l s='Time' mod='ecomzone'}</th>
|
||||
<th>{l s='Level' mod='ecomzone'}</th>
|
||||
<th>{l s='Message' mod='ecomzone'}</th>
|
||||
<th>{l s='Details' mod='ecomzone'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{if isset($ACTIVITY_LOG) && $ACTIVITY_LOG}
|
||||
{foreach from=$ACTIVITY_LOG item=log}
|
||||
<tr class="{if $log.level == 'ERROR'}danger{elseif $log.level == 'WARNING'}warning{/if}">
|
||||
<td>{$log.timestamp|escape:'html':'UTF-8'}</td>
|
||||
<td><span class="badge badge-{if $log.level == 'ERROR'}danger{elseif $log.level == 'WARNING'}warning{else}info{/if}">{$log.level|escape:'html':'UTF-8'}</span></td>
|
||||
<td>{$log.message|escape:'html':'UTF-8'}</td>
|
||||
<td>
|
||||
{if $log.details}
|
||||
<button type="button" class="btn btn-xs btn-default" data-toggle="popover" data-content="{$log.details|escape:'html':'UTF-8'}">
|
||||
<i class="icon-info-circle"></i>
|
||||
</button>
|
||||
</form>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{if isset($PAGINATION)}
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<ul class="pagination">
|
||||
{if $PAGINATION.current_page > 1}
|
||||
<li>
|
||||
<a href="{$current_url|escape:'html':'UTF-8'}&page=1" title="{l s='First page' mod='ecomzone'}">
|
||||
<i class="icon-double-angle-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{$current_url|escape:'html':'UTF-8'}&page={$PAGINATION.current_page-1}"
|
||||
title="{l s='Previous page' mod='ecomzone'}">
|
||||
<i class="icon-angle-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{assign var=p value=$PAGINATION.current_page-2}
|
||||
{if $p < 1}
|
||||
{assign var=p value=1}
|
||||
{/if}
|
||||
{assign var=stop value=$p+4}
|
||||
{if $stop > $PAGINATION.total_pages}
|
||||
{assign var=stop value=$PAGINATION.total_pages}
|
||||
{assign var=p value=$stop-4}
|
||||
{if $p < 1}
|
||||
{assign var=p value=1}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{while $p <= $stop}
|
||||
<li {if $p == $PAGINATION.current_page}class="active"{/if}>
|
||||
<a href="{$current_url|escape:'html':'UTF-8'}&page={$p}">{$p}</a>
|
||||
</li>
|
||||
{assign var=p value=$p+1}
|
||||
{/while}
|
||||
|
||||
{if $PAGINATION.current_page < $PAGINATION.total_pages}
|
||||
<li>
|
||||
<a href="{$current_url|escape:'html':'UTF-8'}&page={$PAGINATION.current_page+1}"
|
||||
title="{l s='Next page' mod='ecomzone'}">
|
||||
<i class="icon-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{$current_url|escape:'html':'UTF-8'}&page={$PAGINATION.total_pages}"
|
||||
title="{l s='Last page' mod='ecomzone'}">
|
||||
<i class="icon-double-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h3><i class="icon icon-clock-o"></i> {l s='Automatic Sync' mod='ecomzone'}</h3>
|
||||
<div class="panel-body">
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='Cron URL' mod='ecomzone'}</label>
|
||||
<div class="col-lg-9">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" readonly="readonly"
|
||||
value="{$shop_url}modules/ecomzone/cron.php?token={$ECOMZONE_CRON_TOKEN|escape:'html':'UTF-8'}" />
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" onclick="selectText(this.parentElement.previousElementSibling)">
|
||||
<i class="icon-copy"></i> {l s='Copy' mod='ecomzone'}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
{l s='Add this URL to your server\'s crontab to run every 24 hours.' mod='ecomzone'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{else}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">{l s='No activity logs available' mod='ecomzone'}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{if isset($ECOMZONE_LOGS) && $ECOMZONE_LOGS}
|
||||
<div class="panel">
|
||||
<h3><i class="icon icon-list"></i> {l s='Recent Logs' mod='ecomzone'}</h3>
|
||||
<div class="panel-body">
|
||||
<div class="log-container" style="max-height: 400px; overflow-y: auto;">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
{foreach from=$ECOMZONE_LOGS item=log}
|
||||
<tr>
|
||||
<td style="font-family: monospace;">{$log|escape:'html':'UTF-8'}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<script type="text/javascript">
|
||||
function selectText(element) {
|
||||
element.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showSuccessMessage('{l s='Copied to clipboard!' mod='ecomzone'}');
|
||||
} catch (err) {
|
||||
showErrorMessage('{l s='Failed to copy. Please copy manually.' mod='ecomzone'}');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.log-container {
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
.progress {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.log-container table {
|
||||
margin-bottom: 0;
|
||||
.badge-success {
|
||||
background-color: #72C279;
|
||||
}
|
||||
.input-group .form-control {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
.badge-warning {
|
||||
background-color: #fbbb22;
|
||||
}
|
||||
.badge-danger {
|
||||
background-color: #E08F95;
|
||||
}
|
||||
.badge-info {
|
||||
background-color: #25B9D7;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="popover"]').popover({
|
||||
placement: 'left',
|
||||
trigger: 'click'
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
var syncInProgress = false;
|
||||
var currentRequest = null;
|
||||
|
||||
function showErrorMessage(message) {
|
||||
if (typeof $.growl !== 'undefined') {
|
||||
$.growl.error({ message: message });
|
||||
} else {
|
||||
alert(message);
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccessMessage(message) {
|
||||
if (typeof $.growl !== 'undefined') {
|
||||
$.growl.notice({ message: message });
|
||||
}
|
||||
}
|
||||
|
||||
function handleAjaxError(xhr, status, error) {
|
||||
var errorMessage = '{l s='Connection error' mod='ecomzone'}';
|
||||
|
||||
if (xhr.responseText) {
|
||||
try {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
if (response.error) {
|
||||
errorMessage = response.error;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to parse error response:', e);
|
||||
}
|
||||
}
|
||||
|
||||
showErrorMessage(errorMessage);
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
$('#sync-progress').hide();
|
||||
}
|
||||
|
||||
function updateProgress(progress) {
|
||||
if (!progress) return;
|
||||
|
||||
$('#sync-progress').show();
|
||||
$('.progress-bar').css('width', progress.percentage + '%').text(progress.percentage + '%');
|
||||
$('#sync-status').text('{l s='Processed:' mod='ecomzone'} ' + progress.processed + ' / ' + progress.total);
|
||||
}
|
||||
|
||||
function continueSyncProcess() {
|
||||
if (!syncInProgress) return false;
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: '{$current_url|escape:'javascript':'UTF-8'}',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
ajax: 1,
|
||||
action: 'continueSyncProducts',
|
||||
token: '{$token|escape:'javascript':'UTF-8'}'
|
||||
},
|
||||
success: function(result) {
|
||||
if (!result) {
|
||||
showErrorMessage('{l s='Invalid server response' mod='ecomzone'}');
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
$('#sync-progress').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
updateProgress(result.progress);
|
||||
if (!result.progress.is_complete) {
|
||||
setTimeout(continueSyncProcess, 1000);
|
||||
} else {
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
showSuccessMessage('{l s='Synchronization completed successfully' mod='ecomzone'}');
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
showErrorMessage(result.error || '{l s='Sync failed' mod='ecomzone'}');
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
$('#sync-progress').hide();
|
||||
}
|
||||
},
|
||||
error: handleAjaxError
|
||||
});
|
||||
}
|
||||
|
||||
$('#sync-form').on('submit', function(e) {
|
||||
e.preventDefault();
|
||||
if (syncInProgress) return false;
|
||||
|
||||
syncInProgress = true;
|
||||
$('#sync-button').prop('disabled', true);
|
||||
$('#sync-progress').show();
|
||||
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
|
||||
currentRequest = $.ajax({
|
||||
url: $(this).attr('action'),
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: $(this).serialize() + '&ajax=1&syncProducts=1',
|
||||
success: function(result) {
|
||||
if (!result) {
|
||||
showErrorMessage('{l s='Invalid server response' mod='ecomzone'}');
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
$('#sync-progress').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
updateProgress(result.progress);
|
||||
if (!result.progress.is_complete) {
|
||||
setTimeout(continueSyncProcess, 1000);
|
||||
} else {
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
showSuccessMessage('{l s='Synchronization completed successfully' mod='ecomzone'}');
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
showErrorMessage(result.error || '{l s='Sync failed' mod='ecomzone'}');
|
||||
syncInProgress = false;
|
||||
$('#sync-button').prop('disabled', false);
|
||||
$('#sync-progress').hide();
|
||||
}
|
||||
},
|
||||
error: handleAjaxError
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$(window).on('beforeunload', function() {
|
||||
if (currentRequest) {
|
||||
currentRequest.abort();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user