backup
This commit is contained in:
1 parent
6086c048cd
commit
0b8c5de8b0
9 files changed
+876
No files matched your search
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
class EcomZoneClient
|
||||
{
|
||||
private $apiToken;
|
||||
private $apiUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Hardcoded API token for testing
|
||||
$this->apiToken = 'klRyAdrXaxL0s6PEUp7LDlH6T8aPSCtBY8NiEHsHiWpc6646K2TZPi5KMxUg';
|
||||
$this->apiUrl = Configuration::get('ECOMZONE_API_URL');
|
||||
|
||||
if (empty($this->apiUrl)) {
|
||||
throw new Exception('API URL not configured');
|
||||
}
|
||||
}
|
||||
|
||||
public function getCatalog($page = 1, $perPage = 100)
|
||||
{
|
||||
$url = $this->apiUrl . '/catalog?page=' . $page . '&per_page=' . $perPage;
|
||||
|
||||
EcomZoneLogger::log("Making API request", 'INFO', ['url' => $url]);
|
||||
return $this->makeRequest('GET', $url);
|
||||
}
|
||||
|
||||
public function getProduct($sku)
|
||||
{
|
||||
return $this->makeRequest('GET', $this->apiUrl . '/product/' . $sku);
|
||||
}
|
||||
|
||||
public function createOrder($orderData)
|
||||
{
|
||||
return $this->makeRequest('POST', $this->apiUrl . '/ordering', $orderData);
|
||||
}
|
||||
|
||||
public function getOrder($orderId)
|
||||
{
|
||||
return $this->makeRequest('GET', $this->apiUrl . '/order/' . $orderId);
|
||||
}
|
||||
|
||||
private function makeRequest($method, $url, $data = null)
|
||||
{
|
||||
$retryCount = 0;
|
||||
$maxRetries = 5;
|
||||
$backoff = 1; // initial backoff time in seconds
|
||||
do {
|
||||
$curl = curl_init();
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . $this->apiToken,
|
||||
'Accept: application/json',
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
|
||||
$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 => $headers,
|
||||
CURLOPT_SSL_VERIFYPEER => false, // Temporarily disable SSL verification for testing
|
||||
];
|
||||
|
||||
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);
|
||||
|
||||
EcomZoneLogger::log("API Response", 'INFO', [
|
||||
'url' => $url,
|
||||
'method' => $method,
|
||||
'http_code' => $httpCode,
|
||||
'curl_error' => $err,
|
||||
'response' => $response
|
||||
]);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($err) {
|
||||
if (strpos($err, 'Connection timed out') !== false && $retryCount < $maxRetries) {
|
||||
EcomZoneLogger::log("Connection timed out. Retrying in $backoff seconds...", 'WARNING');
|
||||
sleep($backoff);
|
||||
$backoff *= 2; // exponential backoff
|
||||
$retryCount++;
|
||||
} else {
|
||||
throw new Exception('cURL Error: ' . $err);
|
||||
}
|
||||
} else if ($httpCode >= 400) {
|
||||
throw new Exception('API Error: HTTP ' . $httpCode . ' - ' . $response);
|
||||
} else {
|
||||
$decodedResponse = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('Invalid JSON response: ' . json_last_error_msg());
|
||||
}
|
||||
return $decodedResponse;
|
||||
}
|
||||
} while ($retryCount < $maxRetries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class EcomZoneCronTask extends ModuleGrid
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->name = 'ecomzone_cron';
|
||||
$this->title = $this->l('EcomZone Product Sync');
|
||||
$this->cron_frequency = 3600 * 2; // Run every hour, change this to 2 hours!!!
|
||||
}
|
||||
|
||||
public function install()
|
||||
{
|
||||
if (!parent::install()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Configuration::updateValue('ECOMZONE_LAST_CRON_RUN', '');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
try {
|
||||
EcomZoneLogger::log('Starting scheduled product sync');
|
||||
|
||||
$productSync = new EcomZoneProductSync();
|
||||
$result = $produgtSync->importProducts();
|
||||
|
||||
Configuration::updateValue('ECOMZONE_LAST_CRON_RUN', date('Y-m-d H:i:s'));
|
||||
EcomZoneLogger::log('Scheduled product sync completed', 'INFO', $result);
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log('Scheduled product sync failed', 'ERROR', ['error' => $e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
class EcomZoneLogger
|
||||
{
|
||||
const LOG_FILE = _PS_ROOT_DIR_ . '/var/logs/ecomzone.log';
|
||||
|
||||
public static function log($message, $level = 'INFO', $context = [])
|
||||
{
|
||||
$date = date('Y-m-d H:i:s');
|
||||
$contextStr = !empty($context) ? json_encode($context) : '';
|
||||
$logMessage = "[$date] [$level] $message $contextStr\n";
|
||||
|
||||
file_put_contents(self::LOG_FILE, $logMessage, FILE_APPEND);
|
||||
|
||||
// Also log to PrestaShop
|
||||
PrestaShopLogger::addLog(
|
||||
"EcomZone: $message",
|
||||
($level === 'ERROR' ? 3 : 1),
|
||||
null,
|
||||
'EcomZone',
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
class EcomZoneOrderSync
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new EcomZoneClient();
|
||||
}
|
||||
|
||||
public function syncOrder($orderId)
|
||||
{
|
||||
try {
|
||||
EcomZoneLogger::log("Starting order sync", 'INFO', ['order_id' => $orderId]);
|
||||
|
||||
$order = new Order($orderId);
|
||||
$customer = new Customer($order->id_customer);
|
||||
$address = new Address($order->id_address_delivery);
|
||||
|
||||
$orderData = $this->prepareOrderData($order, $customer, $address);
|
||||
$result = $this->client->createOrder($orderData);
|
||||
|
||||
EcomZoneLogger::log("Order sync completed", 'INFO', [
|
||||
'order_id' => $orderId,
|
||||
'result' => $result
|
||||
]);
|
||||
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Order sync failed", 'ERROR', [
|
||||
'order_id' => $orderId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function prepareOrderData($order, $customer, $address)
|
||||
{
|
||||
return [
|
||||
'order_index' => $order->id,
|
||||
'ext_id' => $order->reference,
|
||||
'payment' => [
|
||||
'method' => $this->getPaymentMethod($order),
|
||||
'customer_price' => $order->total_paid
|
||||
],
|
||||
'customer_data' => [
|
||||
'full_name' => $customer->firstname . ' ' . $customer->lastname,
|
||||
'email' => $customer->email,
|
||||
'phone_number' => $address->phone,
|
||||
'country' => Country::getIsoById($address->id_country),
|
||||
'address' => $address->address1 . ' ' . $address->address2,
|
||||
'city' => $address->city,
|
||||
'post_code' => $address->postcode
|
||||
],
|
||||
'items' => $this->getOrderItems($order)
|
||||
];
|
||||
}
|
||||
|
||||
private function getPaymentMethod($order)
|
||||
{
|
||||
// Map PrestaShop payment modules to eComZone payment methods
|
||||
return $order->module === 'cashondelivery' ? 'cod' : 'pp';
|
||||
}
|
||||
|
||||
private function getOrderItems($order)
|
||||
{
|
||||
$items = [];
|
||||
$products = $order->getProducts();
|
||||
|
||||
foreach ($products as $product) {
|
||||
$items[] = [
|
||||
'full_sku' => $product['reference'],
|
||||
'quantity' => $product['product_quantity']
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
class EcomZoneProductSync
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->client = new EcomZoneClient();
|
||||
}
|
||||
|
||||
private function resizeImage($src, $dest, $width, $height)
|
||||
{
|
||||
list($srcWidth, $srcHeight, $type) = getimagesize($src);
|
||||
$srcImage = $this->createImageFromType($src, $type);
|
||||
|
||||
$destImage = imagecreatetruecolor($width, $height);
|
||||
imagecopyresampled($destImage, $srcImage, 0, 0, 0, 0, $width, $height, $srcWidth, $srcHeight);
|
||||
|
||||
$this->saveImageFromType($destImage, $dest, $type);
|
||||
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($destImage);
|
||||
}
|
||||
|
||||
private function createImageFromType($filename, $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 saveImageFromType($image, $filename, $type)
|
||||
{
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
$this->createDirectoryIfNotExists(dirname($filename));
|
||||
imagejpeg($image, $filename);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$this->createDirectoryIfNotExists(dirname($filename));
|
||||
imagepng($image, $filename);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
$this->createDirectoryIfNotExists(dirname($filename));
|
||||
imagegif($image, $filename);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
imagegif($image, $filename);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupported image type: " . $type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function importProducts($perPage = 100)
|
||||
{
|
||||
$page = 1;
|
||||
$totalImported = 0;
|
||||
$totalAvailable = 0;
|
||||
|
||||
EcomZoneLogger::log("Starting product import - perPage: $perPage");
|
||||
|
||||
EcomZoneLogger::log("Number of products to be imported per page: $perPage", 'INFO');
|
||||
|
||||
try {
|
||||
do {
|
||||
$catalog = $this->client->getCatalog($page, $perPage);
|
||||
$importedCount = 0;
|
||||
foreach ($catalog['data'] as $product) {
|
||||
if ($this->importSingleProduct($product)) {
|
||||
$importedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
$totalImported += $importedCount;
|
||||
$totalAvailable = $catalog['total'];
|
||||
$page++;
|
||||
|
||||
EcomZoneLogger::log("Imported page $page", 'INFO', [
|
||||
'total_imported' => $totalImported,
|
||||
'page' => $page,
|
||||
'total_available' => $totalAvailable
|
||||
]);
|
||||
|
||||
} while ($page <= ceil($totalAvailable / $perPage) && isset($catalog['next_page_url']) && $catalog['next_page_url'] !== null);
|
||||
|
||||
EcomZoneLogger::log("Finished importing products", 'INFO', [
|
||||
'total_imported' => $totalImported,
|
||||
'total_available' => $totalAvailable
|
||||
]);
|
||||
|
||||
// Clear cache
|
||||
Tools::clearSmartyCache();
|
||||
Tools::clearXMLCache();
|
||||
Media::clearCache();
|
||||
PrestaShopAutoload::getInstance()->generateIndex();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'imported' => $totalImported,
|
||||
'total' => $totalAvailable
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Error importing products: " . $e->getMessage(), 'ERROR');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function importSingleProduct($productData)
|
||||
{
|
||||
// Extract data from nested structure
|
||||
$data = isset($productData['data']) ? $productData['data'] : $productData;
|
||||
|
||||
if (!isset($data['sku']) || !isset($data['product_name']) || !isset($data['description']) || !isset($data['product_price'])) {
|
||||
EcomZoneLogger::log("Invalid product data", 'ERROR', ['data' => $productData]);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if product already exists by reference
|
||||
$productId = Db::getInstance()->getValue('
|
||||
SELECT id_product
|
||||
FROM ' . _DB_PREFIX_ . 'product
|
||||
WHERE reference = "' . pSQL($data['sku']) . '"
|
||||
');
|
||||
|
||||
$product = $productId ? new Product($productId) : new Product();
|
||||
|
||||
$defaultLangId = (int)Configuration::get('PS_LANG_DEFAULT');
|
||||
|
||||
$product->reference = $data['sku'];
|
||||
$product->name[$defaultLangId] = $data['product_name'];
|
||||
$product->description[$defaultLangId] = $data['long_description'] ?? $data['description'];
|
||||
$product->description_short[$defaultLangId] = $data['description'];
|
||||
$product->price = $data['product_price'];
|
||||
$product->active = true;
|
||||
$product->quantity = (int)$data['stock'];
|
||||
$homeCategoryId = (int)Configuration::get('PS_HOME_CATEGORY');
|
||||
$product->id_category_default = $homeCategoryId;
|
||||
$product->addToCategories([$homeCategoryId]);
|
||||
$product->visibility = 'both'; // Ensure product is visible in both catalog and search
|
||||
$product->active = true;
|
||||
$product->quantity = (int)$data['stock'];
|
||||
$homeCategoryId = (int)Configuration::get('PS_HOME_CATEGORY');
|
||||
$product->id_category_default = $homeCategoryId;
|
||||
$product->addToCategories([$homeCategoryId]);
|
||||
|
||||
|
||||
// Save product first to get ID
|
||||
if (!$product->id) {
|
||||
$product->add();
|
||||
} else {
|
||||
$product->update();
|
||||
}
|
||||
StockAvailable::setQuantity($product->id, 0, (int)$data['stock']);
|
||||
$product->available_for_order = true; // Ensure product is available for order
|
||||
$product->show_price = true; // Ensure price is shown
|
||||
|
||||
// Handle image import if URL is provided
|
||||
if (isset($data['image']) && !empty($data['image'])) {
|
||||
$this->importProductImage($product, $data['image']);
|
||||
}
|
||||
|
||||
StockAvailable::setQuantity($product->id, 0, (int)$data['stock']);
|
||||
|
||||
EcomZoneLogger::log("Imported product", 'INFO', [
|
||||
'sku' => $data['sku'],
|
||||
'id' => $product->id,
|
||||
'name' => $data['product_name']
|
||||
]);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Error importing product", 'ERROR', [
|
||||
'sku' => $data['sku'],
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function importProductImage($product, $imageUrl)
|
||||
{
|
||||
try {
|
||||
// Create temporary file
|
||||
$tmpFile = tempnam(_PS_TMP_IMG_DIR_, 'ecomzone_');
|
||||
|
||||
// Download image
|
||||
if (!copy($imageUrl, $tmpFile)) {
|
||||
throw new Exception("Failed to download image from: " . $imageUrl);
|
||||
}
|
||||
|
||||
// Get image info
|
||||
$imageInfo = getimagesize($tmpFile);
|
||||
if (!$imageInfo) {
|
||||
unlink($tmpFile);
|
||||
throw new Exception("Invalid image file");
|
||||
}
|
||||
|
||||
// Validate image dimensions and file size
|
||||
if ($imageInfo[0] > 2000 || $imageInfo[1] > 2000 || filesize($tmpFile) > 5000000) {
|
||||
unlink($tmpFile);
|
||||
throw new Exception("Image dimensions or file size exceed limits");
|
||||
}
|
||||
|
||||
// Generate unique name
|
||||
$imageName = $product->reference . '-' . time() . '.' . pathinfo($imageUrl, PATHINFO_EXTENSION);
|
||||
|
||||
// Delete existing images if any
|
||||
$product->deleteImages();
|
||||
|
||||
// Add new image
|
||||
$image = new Image();
|
||||
$image->id_product = $product->id;
|
||||
$image->position = 1;
|
||||
$image->cover = true;
|
||||
|
||||
// Save the image to the correct directory
|
||||
$imagePath = _PS_PROD_IMG_DIR_ . $image->getImgPath() . '.' . $image->image_format;
|
||||
$this->createDirectoryIfNotExists(dirname($imagePath));
|
||||
if (!copy($tmpFile, $imagePath)) {
|
||||
unlink($tmpFile);
|
||||
throw new Exception("Failed to save image to: " . $imagePath);
|
||||
}
|
||||
|
||||
// Associate the image with the product
|
||||
if (!$image->add()) {
|
||||
unlink($tmpFile);
|
||||
throw new Exception("Failed to add image to product");
|
||||
}
|
||||
|
||||
// Manually resize the image and generate thumbnails
|
||||
$this->resizeImage($imagePath, _PS_PROD_IMG_DIR_ . $image->getImgPath() . '-home_default.' . $image->image_format, 250, 250);
|
||||
$this->resizeImage($imagePath, _PS_PROD_IMG_DIR_ . $image->getImgPath() . '-large_default.' . $image->image_format, 800, 800);
|
||||
|
||||
// Cleanup
|
||||
unlink($tmpFile);
|
||||
EcomZoneLogger::log("Imported product image", 'INFO', [
|
||||
'sku' => $product->reference,
|
||||
'image' => $imageUrl
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log("Error importing product image", 'ERROR', [
|
||||
'sku' => $product->reference,
|
||||
'image' => $imageUrl,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function createDirectoryIfNotExists($directory)
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user