working version
This commit is contained in:
parent
184cac66e6
commit
7e0aa41be6
BIN
ecomzone-v3.1.zip
Normal file
BIN
ecomzone-v3.1.zip
Normal file
Binary file not shown.
BIN
ecomzone-v5.1.zip
Normal file
BIN
ecomzone-v5.1.zip
Normal file
Binary file not shown.
BIN
ecomzone-v5.zip
Normal file
BIN
ecomzone-v5.zip
Normal file
Binary file not shown.
107
ecomzone/classes/EcomZoneClient.php
Normal file
107
ecomzone/classes/EcomZoneClient.php
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
40
ecomzone/classes/EcomZoneCronTask.php
Normal file
40
ecomzone/classes/EcomZoneCronTask.php
Normal file
@ -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
|
||||
}
|
||||
|
||||
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 = $productSync->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;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
ecomzone/classes/EcomZoneLogger.php
Normal file
25
ecomzone/classes/EcomZoneLogger.php
Normal file
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
81
ecomzone/classes/EcomZoneOrderSync.php
Normal file
81
ecomzone/classes/EcomZoneOrderSync.php
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
270
ecomzone/classes/EcomZoneProductSync.php
Normal file
270
ecomzone/classes/EcomZoneProductSync.php
Normal file
@ -0,0 +1,270 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
ecomzone/cron.php
Normal file
43
ecomzone/cron.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
include(dirname(__FILE__) . '/../../config/config.inc.php');
|
||||
include(dirname(__FILE__) . '/../../init.php');
|
||||
|
||||
// Include the module class
|
||||
require_once dirname(__FILE__) . '/ecomzone.php';
|
||||
|
||||
// Ensure module is loaded
|
||||
$module = Module::getInstanceByName('ecomzone');
|
||||
if (!$module) {
|
||||
die('Module not found');
|
||||
}
|
||||
|
||||
// Security token check
|
||||
$token = null;
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
global $argv;
|
||||
foreach ($argv as $arg) {
|
||||
if (preg_match('/^--token=(.+)$/', $arg, $matches)) {
|
||||
$token = $matches[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$token = Tools::getValue('token');
|
||||
}
|
||||
|
||||
$configToken = Configuration::get('ECOMZONE_CRON_TOKEN');
|
||||
|
||||
echo "Config token: " . $configToken . "\n";
|
||||
echo "Provided token: " . $token . "\n";
|
||||
|
||||
if (empty($token) || $token !== $configToken) {
|
||||
die('Invalid token. Provided: ' . $token . ', Expected: ' . $configToken);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $module->runCronTasks();
|
||||
echo json_encode(['success' => true, 'result' => $result]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
219
ecomzone/ecomzone.php
Normal file
219
ecomzone/ecomzone.php
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
if (!defined('_PS_VERSION_')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class EcomZone extends Module
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'ecomzone';
|
||||
$this->tab = 'market_place';
|
||||
$this->version = '1.0.0';
|
||||
$this->author = 'Your Name';
|
||||
$this->need_instance = 0;
|
||||
$this->bootstrap = true;
|
||||
|
||||
parent::__construct();
|
||||
|
||||
// Register autoloader
|
||||
spl_autoload_register([$this, 'autoload']);
|
||||
|
||||
$this->displayName = $this->l('EcomZone Dropshipping');
|
||||
$this->description = $this->l('Integration with EcomZone Dropshipping API');
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoload classes
|
||||
*/
|
||||
public function autoload($className)
|
||||
{
|
||||
// Only handle our module's classes
|
||||
if (strpos($className, 'EcomZone') !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$classPath = dirname(__FILE__) . '/classes/' . $className . '.php';
|
||||
if (file_exists($classPath)) {
|
||||
require_once $classPath;
|
||||
}
|
||||
}
|
||||
|
||||
public function install()
|
||||
{
|
||||
return parent::install() &&
|
||||
$this->registerHook('actionOrderStatusUpdate') &&
|
||||
Configuration::updateValue('ECOMZONE_API_TOKEN', '') &&
|
||||
Configuration::updateValue('ECOMZONE_API_URL', 'https://dropship.ecomzone.eu/api') &&
|
||||
Configuration::updateValue('ECOMZONE_LAST_SYNC', '') &&
|
||||
Configuration::updateValue('ECOMZONE_CRON_TOKEN', Tools::encrypt(uniqid()));
|
||||
}
|
||||
|
||||
private function createLogFile()
|
||||
{
|
||||
if (!file_exists(dirname(EcomZoneLogger::LOG_FILE))) {
|
||||
mkdir(dirname(EcomZoneLogger::LOG_FILE), 0755, true);
|
||||
}
|
||||
|
||||
if (!file_exists(EcomZoneLogger::LOG_FILE)) {
|
||||
touch(EcomZoneLogger::LOG_FILE);
|
||||
chmod(EcomZoneLogger::LOG_FILE, 0666);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function uninstall()
|
||||
{
|
||||
return parent::uninstall() &&
|
||||
Configuration::deleteByName('ECOMZONE_API_TOKEN') &&
|
||||
Configuration::deleteByName('ECOMZONE_API_URL') &&
|
||||
Configuration::deleteByName('ECOMZONE_CRON_TOKEN') &&
|
||||
Configuration::deleteByName('ECOMZONE_LAST_CRON_RUN');
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
$output = '';
|
||||
|
||||
if (Tools::isSubmit('submitEcomZoneModule')) {
|
||||
Configuration::updateValue('ECOMZONE_API_TOKEN', Tools::getValue('ECOMZONE_API_TOKEN'));
|
||||
$output .= $this->displayConfirmation($this->l('Settings updated'));
|
||||
}
|
||||
|
||||
// Handle manual product import
|
||||
if (Tools::isSubmit('importP<Down>oducts')) {
|
||||
try {
|
||||
$productSync = new EcomZoneProductSync();
|
||||
$result = $productSync->importProducts();
|
||||
$output .= $this->displayConfirmation(
|
||||
sprintf($this->l('Imported %d products'), $result['imported'])
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$output .= $this->displayError($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Add debug info
|
||||
$debugInfo = $this->getDebugInfo();
|
||||
|
||||
$shopUrl = Tools::getShopDomainSsl(true);
|
||||
$shopRoot = _PS_ROOT_DIR_;
|
||||
|
||||
$this->context->smarty->assign([
|
||||
'ECOMZONE_API_TOKEN' => Configuration::get('ECOMZONE_API_TOKEN'),
|
||||
'ECOMZONE_CRON_TOKEN' => Configuration::get('ECOMZONE_CRON_TOKEN'),
|
||||
'ECOMZONE_DEBUG_INFO' => $debugInfo,
|
||||
'ECOMZONE_LOGS' => $this->getRecentLogs(),
|
||||
'shop_url' => $shopUrl,
|
||||
'shop_root' => $shopRoot
|
||||
]);
|
||||
|
||||
return $output . $this->display(__FILE__, 'views/templates/admin/configure.tpl');
|
||||
}
|
||||
|
||||
private function getDebugInfo()
|
||||
{
|
||||
$lastCronRun = Configuration::get('ECOMZONE_LAST_CRON_RUN');
|
||||
$nextRun = !empty($lastCronRun) ?
|
||||
date('Y-m-d H:i:s', strtotime($lastCronRun) + 3600) :
|
||||
$this->l('Not scheduled yet');
|
||||
|
||||
return [
|
||||
'php_version' => PHP_VERSION,
|
||||
'prestashop_version' => _PS_VERSION_,
|
||||
'module_version' => $this->version,
|
||||
'curl_enabled' => function_exists('curl_version'),
|
||||
'api_url' => Configuration::get('ECOMZONE_API_URL'),
|
||||
'last_sync' => Configuration::get('ECOMZONE_LAST_SYNC'),
|
||||
'last_cron_run' => $lastCronRun ?: $this->l('Never'),
|
||||
'next_cron_run' => $nextRun,
|
||||
'log_file' => EcomZoneLogger::LOG_FILE,
|
||||
'log_file_exists' => file_exists(EcomZoneLogger::LOG_FILE),
|
||||
'log_file_writable' => is_writable(EcomZoneLogger::LOG_FILE)
|
||||
];
|
||||
}
|
||||
|
||||
private function getRecentLogs($lines = 50)
|
||||
{
|
||||
if (!file_exists(EcomZoneLogger::LOG_FILE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$logs = array_slice(file(EcomZoneLogger::LOG_FILE), -$lines);
|
||||
return array_map('trim', $logs);
|
||||
}
|
||||
|
||||
public function hookActionOrderStatusUpdate($params)
|
||||
{
|
||||
$order = $params['order'];
|
||||
$newOrderStatus = $params['newOrderStatus'];
|
||||
|
||||
// Sync order when it's paid
|
||||
if ($newOrderStatus->paid == 1) {
|
||||
try {
|
||||
$orderSync = new EcomZoneOrderSync();
|
||||
$result = $orderSync->syncOrder($order->id);
|
||||
|
||||
// Log the result
|
||||
PrestaShopLogger::addLog(
|
||||
'EcomZone order sync: ' . json_encode($result),
|
||||
1,
|
||||
null,
|
||||
'Order',
|
||||
$order->id,
|
||||
true
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
PrestaShopLogger::addLog(
|
||||
'EcomZone order sync error: ' . $e->getMessage(),
|
||||
3,
|
||||
null,
|
||||
'Order',
|
||||
$order->id,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function hookActionCronJob($params)
|
||||
{
|
||||
$cronTask = new EcomZoneCronTask();
|
||||
|
||||
// Check if it's time to run
|
||||
$lastRun = Configuration::get('ECOMZONE_LAST_CRON_RUN');
|
||||
if (empty($lastRun) || (strtotime($lastRun) + $cronTask->cron_frequency) <= time()) {
|
||||
return $cronTask->run();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function runCronTasks()
|
||||
{
|
||||
try {
|
||||
EcomZoneLogger::log('Starting scheduled product sync');
|
||||
|
||||
$lastRun = Configuration::get('ECOMZONE_LAST_CRON_RUN');
|
||||
$frequency = 3600; // 1 hour in seconds
|
||||
|
||||
if (!empty($lastRun) && (strtotime($lastRun) + $frequency) > time()) {
|
||||
EcomZoneLogger::log('Skipping cron - too soon since last run');
|
||||
return false;
|
||||
}
|
||||
|
||||
$productSync = new EcomZoneProductSync();
|
||||
$result = $productSync->importProducts();
|
||||
|
||||
Configuration::updateValue('ECOMZONE_LAST_CRON_RUN', date('Y-m-d H:i:s'));
|
||||
EcomZoneLogger::log('Scheduled product sync completed', 'INFO', $result);
|
||||
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
EcomZoneLogger::log('Scheduled product sync failed', 'ERROR', ['error' => $e->getMessage()]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
ecomzone/views/templates/admin/configure.tpl
Normal file
90
ecomzone/views/templates/admin/configure.tpl
Normal file
@ -0,0 +1,90 @@
|
||||
<form method="post" action="{$current|escape:'html':'UTF-8'}&token={$token|escape:'html':'UTF-8'}">
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<i class="icon-cogs"></i> {l s='EcomZone Configuration' mod='ecomzone'}
|
||||
</div>
|
||||
<div class="form-wrapper">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-3">{l s='API Token' mod='ecomzone'}</label>
|
||||
<div class="col-lg-9">
|
||||
<input type="text" name="ECOMZONE_API_TOKEN" value="{$ECOMZONE_API_TOKEN}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<button type="submit" name="submitEcomZoneModule" class="btn btn-default pull-right">
|
||||
<i class="process-icon-save"></i> {l s='Save' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<i class="icon-cogs"></i> {l s='Manual Actions' mod='ecomzone'}
|
||||
</div>
|
||||
<div class="form-wrapper">
|
||||
<form method="post" class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<div class="col-lg-9 col-lg-offset-3">
|
||||
<button type="submit" name="importProducts" class="btn btn-default">
|
||||
<i class="process-icon-download"></i> {l s='Import Products' mod='ecomzone'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<i class="icon-clock"></i> {l s='Cron Setup Instructions' mod='ecomzone'}
|
||||
</div>
|
||||
<div class="form-wrapper">
|
||||
<p>{l s='Add the following command to your server crontab to run every hour:' mod='ecomzone'}</p>
|
||||
<pre>0 * * * * curl -s "{$shop_url}modules/ecomzone/cron.php?token={$ECOMZONE_CRON_TOKEN}"</pre>
|
||||
<p>{l s='Or using PHP CLI:' mod='ecomzone'}</p>
|
||||
<pre>0 * * * * php {$shop_root}modules/ecomzone/cron.php --token={$ECOMZONE_CRON_TOKEN}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<i class="icon-info"></i> {l s='Debug Information' mod='ecomzone'}
|
||||
</div>
|
||||
<div class="form-wrapper">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{foreach from=$ECOMZONE_DEBUG_INFO key=key item=value}
|
||||
<tr>
|
||||
<td><strong>{$key|escape:'html':'UTF-8'}</strong></td>
|
||||
<td>{$value|escape:'html':'UTF-8'}</td>
|
||||
</tr>
|
||||
{/foreach}
|
||||
<tr>
|
||||
<td><strong>{l s='Last Cron Run' mod='ecomzone'}</strong></td>
|
||||
<td>{$ECOMZONE_LAST_CRON_RUN|escape:'html':'UTF-8'}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{l s='Next Scheduled Run' mod='ecomzone'}</strong></td>
|
||||
<td>{$ECOMZONE_NEXT_CRON_RUN|escape:'html':'UTF-8'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<i class="icon-list"></i> {l s='Recent Logs' mod='ecomzone'}
|
||||
</div>
|
||||
<div class="form-wrapper">
|
||||
<div class="log-container" style="max-height: 400px; overflow-y: auto;">
|
||||
{foreach from=$ECOMZONE_LOGS item=log}
|
||||
<div class="log-line">{$log|escape:'html':'UTF-8'}</div>
|
||||
{/foreach}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Reference in New Issue
Block a user