workingCopy/ecomzone/ecomzone.php

346 lines
11 KiB
PHP

<?php
if (!defined("_PS_VERSION_")) {
exit();
}
class Ecomzone extends Module
{
public function __construct()
{
$this->name = "ecomzone";
$this->tab = "shipping_logistics";
$this->version = "1.0.0";
$this->author = "EcomZone";
$this->need_instance = 0;
$this->bootstrap = true;
$this->ps_versions_compliancy = [
"min" => "8.0.0",
"max" => "8.99.99",
];
parent::__construct();
$this->displayName = $this->trans(
"EcomZone Dropshipping",
[],
"Modules.Ecomzone.Admin"
);
$this->description = $this->trans(
"Integration with EcomZone Dropshipping API",
[],
"Modules.Ecomzone.Admin"
);
$this->confirmUninstall = $this->trans(
"Are you sure you want to uninstall?",
[],
"Modules.Ecomzone.Admin"
);
}
public function install()
{
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'
];
foreach ($dirs as $dir) {
if (!file_exists($dir)) {
mkdir($dir, 0755, true);
}
}
}
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");
}
public function getContent()
{
$output = "";
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"
)
);
}
}
// Handle product fetch request
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}"
);
if (!empty($result["data"])) {
$totalPages = ceil($result["total"] / $perPage);
$this->context->smarty->assign([
"API_PRODUCTS" => $result["data"],
"PAGINATION" => [
"total_pages" => $totalPages,
"current_page" => $page,
"total_items" => $result["total"],
],
]);
if (Tools::isSubmit("fetchProducts")) {
$output .= $this->displayConfirmation(
$this->trans(
"Products fetched successfully",
[],
"Modules.Ecomzone.Admin"
)
);
}
}
} catch (Exception $e) {
$output .= $this->displayError($e->getMessage());
}
}
$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_LAST_SYNC" => Configuration::get("ECOMZONE_LAST_SYNC"),
"current_url" =>
$this->context->link->getAdminLink("AdminModules", false) .
"&configure=" .
$this->name .
"&tab_module=" .
$this->tab .
"&module_name=" .
$this->name,
"shop_url" => $this->context->link->getBaseLink(),
]);
return $output .
$this->display(__FILE__, "views/templates/admin/configure.tpl");
}
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"
)
);
}
$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)
{
try {
$lastSync = Configuration::get("ECOMZONE_LAST_SYNC");
$syncInterval = 24 * 3600; // 24 hours
if (
!empty($lastSync) &&
strtotime($lastSync) + $syncInterval > time()
) {
return;
}
$page = 1;
$perPage = 100;
$totalImported = 0;
$errors = [];
do {
$result = $this->makeApiRequest(
"catalog?page={$page}&per_page={$perPage}"
);
if (empty($result["data"])) {
break;
}
foreach ($result["data"] as $product) {
try {
if ($this->importSingleProduct($product)) {
$totalImported++;
}
} catch (Exception $e) {
$errors[] = [
"sku" => $product["sku"] ?? "unknown",
"error" => $e->getMessage()
];
}
// Clear memory after each product
gc_collect_cycles();
}
$page++;
} while (!empty($result["next_page_url"]));
Configuration::updateValue(
"ECOMZONE_LAST_SYNC",
date("Y-m-d H:i:s")
);
} catch (Exception $e) {
EcomZoneLogger::log("Cron job execution failed", "ERROR", [
"error" => $e->getMessage(),
"trace" => $e->getTraceAsString()
]);
}
}
public function importSingleProduct(array $productData): bool
{
try {
$importer = new EcomZoneProductImport();
return $importer->importProduct($productData);
} catch (Exception $e) {
EcomZoneLogger::log("Product import failed", "ERROR", [
"sku" => $productData['data']['sku'] ?? 'unknown',
"error" => $e->getMessage()
]);
return false;
}
}
private function importProductImage(
Product $product,
string $imageUrl,
bool $cover = false
): void {
// Implementation of the method
}
private function updateBasicInfo(Product $product, array $data): void
{
try {
// Basic information
$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->reference = $data["sku"];
$product->ean13 = $data["ean"] ?? "";
// Shop configuration
$product->id_shop_default = $this->defaultShopId;
$product->active = true;
$product->visibility = "both";
$product->available_for_order = true;
$product->show_price = true;
$product->indexed = 1;
// Critical: Save the product
if (!$product->save()) {
throw new EcomZoneException(
"Failed to save product",
EcomZoneException::INVALID_PRODUCT_DATA
);
}
// Add to default shop
$product->addToShop($this->defaultShopId);
} catch (Exception $e) {
throw new EcomZoneException(
"Failed to update product info: " . $e->getMessage(),
EcomZoneException::INVALID_PRODUCT_DATA,
$e
);
}
}
}