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 ); } } }