346 lines
10 KiB
PHP
346 lines
10 KiB
PHP
<?php
|
|
if (!defined("_PS_VERSION_")) {
|
|
exit();
|
|
}
|
|
|
|
class EcomZoneProductImport
|
|
{
|
|
private $module;
|
|
private $defaultLangId;
|
|
private $defaultShopId;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->module = Module::getInstanceByName("ecomzone");
|
|
$this->defaultLangId = (int) Configuration::get("PS_LANG_DEFAULT");
|
|
$this->defaultShopId = (int) Context::getContext()->shop->id;
|
|
}
|
|
|
|
public function importProduct($productData)
|
|
{
|
|
try {
|
|
$data = $productData["data"];
|
|
|
|
// Start transaction
|
|
Db::getInstance()->execute("START TRANSACTION");
|
|
|
|
// Get or create product
|
|
$product = $this->getOrCreateProduct($data["sku"]);
|
|
|
|
// Update basic information
|
|
$this->updateBasicInfo($product, $data);
|
|
|
|
// Update categories
|
|
$this->updateCategories($product, $data);
|
|
|
|
// Update images
|
|
$this->updateImages($product, $data);
|
|
|
|
// Update specifications
|
|
$this->updateSpecifications($product, $data);
|
|
|
|
// Update stock
|
|
$this->updateStock($product, $data);
|
|
|
|
// Commit transaction
|
|
Db::getInstance()->execute("COMMIT");
|
|
|
|
EcomZoneLogger::log("Product imported successfully", "INFO", [
|
|
"sku" => $data["sku"],
|
|
]);
|
|
|
|
return true;
|
|
} catch (Exception $e) {
|
|
Db::getInstance()->execute("ROLLBACK");
|
|
EcomZoneLogger::log("Product import failed", "ERROR", [
|
|
"sku" => $data["sku"] ?? "unknown",
|
|
"error" => $e->getMessage(),
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
private function getOrCreateProduct($sku)
|
|
{
|
|
$productId = Product::getIdByReference($sku);
|
|
|
|
if ($productId) {
|
|
$product = new Product($productId);
|
|
} else {
|
|
$product = new Product();
|
|
$product->reference = $sku;
|
|
}
|
|
|
|
return $product;
|
|
}
|
|
|
|
private function updateBasicInfo($product, $data)
|
|
{
|
|
$product->name[$this->defaultLangId] = $data["product_name"];
|
|
$product->description[$this->defaultLangId] = $data["long_description"];
|
|
$product->description_short[$this->defaultLangId] =
|
|
$data["description"];
|
|
$product->price = (float) $data["product_price"];
|
|
$product->wholesale_price =
|
|
(float) ($data["recommended_retail_price"] ?? 0);
|
|
$product->ean13 = $data["ean"] ?? "";
|
|
|
|
// Product settings
|
|
$product->active = true;
|
|
$product->visibility = "both";
|
|
$product->available_for_order = true;
|
|
$product->show_price = true;
|
|
$product->indexed = 1;
|
|
|
|
// Dimensions
|
|
if (!empty($data["dimensions"])) {
|
|
$dimensions = json_decode($data["dimensions"], true);
|
|
$product->weight = (float) ($dimensions["weight"] ?? 0);
|
|
$product->height = (float) ($dimensions["height"] ?? 0);
|
|
$product->width = (float) ($dimensions["width"] ?? 0);
|
|
$product->depth = (float) ($dimensions["length"] ?? 0);
|
|
}
|
|
|
|
if (!$product->id) {
|
|
$product->add();
|
|
} else {
|
|
$product->update();
|
|
}
|
|
}
|
|
|
|
private function updateCategories($product, $data)
|
|
{
|
|
$categoryIds = [];
|
|
|
|
// Process main categories
|
|
if (!empty($data["categories"])) {
|
|
$categories = json_decode($data["categories"], true);
|
|
foreach ($categories as $categoryName) {
|
|
if (strpos($categoryName, "0") === 0) {
|
|
continue;
|
|
} // Skip special categories
|
|
$categoryId = $this->getOrCreateCategory($categoryName);
|
|
if ($categoryId) {
|
|
$categoryIds[] = $categoryId;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process Google categories
|
|
if (!empty($data["google_categories"])) {
|
|
$googleCategories = json_decode($data["google_categories"], true);
|
|
foreach ($googleCategories as $categoryName) {
|
|
$categoryId = $this->getOrCreateCategory($categoryName);
|
|
if ($categoryId) {
|
|
$categoryIds[] = $categoryId;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure we have at least default category
|
|
if (empty($categoryIds)) {
|
|
$categoryIds[] = (int) Configuration::get("PS_HOME_CATEGORY");
|
|
}
|
|
|
|
$categoryIds = array_unique($categoryIds);
|
|
$product->id_category_default = reset($categoryIds);
|
|
$product->updateCategories($categoryIds);
|
|
}
|
|
|
|
private function getOrCreateCategory($name)
|
|
{
|
|
$name = trim($name);
|
|
if (empty($name)) {
|
|
return null;
|
|
}
|
|
|
|
// Try to find existing category
|
|
$categoryId = (int) Db::getInstance()->getValue(
|
|
'SELECT c.id_category
|
|
FROM `' .
|
|
_DB_PREFIX_ .
|
|
'category_lang` cl
|
|
JOIN `' .
|
|
_DB_PREFIX_ .
|
|
'category` c ON c.id_category = cl.id_category
|
|
WHERE cl.name = "' .
|
|
pSQL($name) .
|
|
'"
|
|
AND cl.id_lang = ' .
|
|
(int) $this->defaultLangId
|
|
);
|
|
|
|
if ($categoryId) {
|
|
return $categoryId;
|
|
}
|
|
|
|
// Create new category
|
|
$category = new Category();
|
|
$category->name = [$this->defaultLangId => $name];
|
|
$category->link_rewrite = [
|
|
$this->defaultLangId => Tools::str2url($name),
|
|
];
|
|
$category->id_parent = (int) Configuration::get("PS_HOME_CATEGORY");
|
|
$category->active = true;
|
|
|
|
if ($category->add()) {
|
|
return (int) $category->id;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function updateImages($product, $data)
|
|
{
|
|
$imageUrls = [];
|
|
|
|
// Main image
|
|
if (!empty($data["image"])) {
|
|
$imageUrls[] = $data["image"];
|
|
}
|
|
|
|
// Additional images
|
|
if (!empty($data["images"])) {
|
|
$additionalImages = json_decode($data["images"], true);
|
|
if (is_array($additionalImages)) {
|
|
$imageUrls = array_merge($imageUrls, $additionalImages);
|
|
}
|
|
}
|
|
|
|
// GIF or video thumbnail
|
|
if (!empty($data["gif_or_video"])) {
|
|
$imageUrls[] = $data["gif_or_video"];
|
|
}
|
|
|
|
if (empty($imageUrls)) {
|
|
return;
|
|
}
|
|
|
|
// Delete existing images
|
|
$product->deleteImages();
|
|
|
|
// Import new images
|
|
foreach ($imageUrls as $index => $imageUrl) {
|
|
$this->importImage($product, $imageUrl, $index === 0);
|
|
}
|
|
}
|
|
|
|
private function importImage($product, $imageUrl, $cover = false)
|
|
{
|
|
$tmpFile = tempnam(_PS_TMP_IMG_DIR_, "ecomzone_");
|
|
|
|
try {
|
|
if (!copy($imageUrl, $tmpFile)) {
|
|
throw new Exception("Failed to download image");
|
|
}
|
|
|
|
$image = new Image();
|
|
$image->id_product = $product->id;
|
|
$image->position = Image::getHighestPosition($product->id) + 1;
|
|
$image->cover = $cover;
|
|
|
|
if (!$image->add()) {
|
|
throw new Exception("Failed to create image record");
|
|
}
|
|
|
|
$imagesTypes = ImageType::getImagesTypes("products");
|
|
foreach ($imagesTypes as $type) {
|
|
ImageManager::resize(
|
|
$tmpFile,
|
|
_PS_PROD_IMG_DIR_ .
|
|
$image->getExistingImgPath() .
|
|
"." .
|
|
$type["name"] .
|
|
".jpg",
|
|
$type["width"],
|
|
$type["height"]
|
|
);
|
|
}
|
|
|
|
// Save original image
|
|
ImageManager::resize(
|
|
$tmpFile,
|
|
_PS_PROD_IMG_DIR_ . $image->getExistingImgPath() . ".jpg"
|
|
);
|
|
} catch (Exception $e) {
|
|
EcomZoneLogger::log("Image import failed", "ERROR", [
|
|
"url" => $imageUrl,
|
|
"error" => $e->getMessage(),
|
|
]);
|
|
} finally {
|
|
if (file_exists($tmpFile)) {
|
|
unlink($tmpFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function updateSpecifications($product, $data)
|
|
{
|
|
if (empty($data["specifications"])) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$specifications = json_decode($data["specifications"], true);
|
|
if (!is_array($specifications)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($specifications as $name => $value) {
|
|
if (empty($value)) {
|
|
continue;
|
|
}
|
|
|
|
// Get or create feature
|
|
$featureId = Feature::getIdByName($name);
|
|
if (!$featureId) {
|
|
$feature = new Feature();
|
|
$feature->name[$this->defaultLangId] = $name;
|
|
if (!$feature->add()) {
|
|
continue;
|
|
}
|
|
$featureId = $feature->id;
|
|
}
|
|
|
|
// Get or create feature value
|
|
$featureValue = new FeatureValue();
|
|
$featureValue->id_feature = $featureId;
|
|
$featureValue->value[$this->defaultLangId] = $value;
|
|
if (!$featureValue->add()) {
|
|
continue;
|
|
}
|
|
|
|
// Associate with product
|
|
$product->addFeaturesToDB([$featureId => $featureValue->id]);
|
|
}
|
|
} catch (Exception $e) {
|
|
EcomZoneLogger::log("Failed to update specifications", "ERROR", [
|
|
"sku" => $product->reference,
|
|
"error" => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function updateStock($product, $data)
|
|
{
|
|
try {
|
|
$quantity = (int) ($data["stock_quantity"] ?? $data["stock"] ?? 0);
|
|
StockAvailable::setQuantity($product->id, 0, $quantity);
|
|
|
|
// Update out of stock behavior
|
|
StockAvailable::setProductOutOfStock(
|
|
$product->id,
|
|
$quantity > 0 ? 2 : 0, // 2 = Allow orders, 0 = Deny orders
|
|
$this->defaultShopId
|
|
);
|
|
} catch (Exception $e) {
|
|
throw new EcomZoneException(
|
|
"Failed to update stock: " . $e->getMessage(),
|
|
EcomZoneException::INVALID_PRODUCT_DATA,
|
|
$e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|