775 lines
29 KiB
PHP
775 lines
29 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();
|
|
|
|
// Register autoloader
|
|
spl_autoload_register([$this, 'autoload']);
|
|
|
|
$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"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Autoload EcomZone classes
|
|
*/
|
|
public function autoload($className)
|
|
{
|
|
// 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'
|
|
];
|
|
|
|
// 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()
|
|
{
|
|
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()
|
|
{
|
|
// 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");
|
|
$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 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}",
|
|
"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([
|
|
"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) {
|
|
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_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=" .
|
|
$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 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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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}",
|
|
"GET"
|
|
);
|
|
|
|
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
|
|
);
|
|
}
|
|
}
|
|
|
|
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()
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|