From 7e0aa41be6001f4fd3f0aef221e8c39fdaaa6aa0 Mon Sep 17 00:00:00 2001 From: dimitar Date: Thu, 30 Jan 2025 02:58:14 +0100 Subject: [PATCH] working version --- ecomzone-v3.1.zip | Bin 0 -> 10513 bytes ecomzone-v5.1.zip | Bin 0 -> 10838 bytes ecomzone-v5.zip | Bin 0 -> 10674 bytes ecomzone/classes/EcomZoneClient.php | 107 ++++++++ ecomzone/classes/EcomZoneCronTask.php | 40 +++ ecomzone/classes/EcomZoneLogger.php | 25 ++ ecomzone/classes/EcomZoneOrderSync.php | 81 ++++++ ecomzone/classes/EcomZoneProductSync.php | 270 +++++++++++++++++++ ecomzone/cron.php | 43 +++ ecomzone/ecomzone.php | 219 +++++++++++++++ ecomzone/views/templates/admin/configure.tpl | 90 +++++++ 11 files changed, 875 insertions(+) create mode 100644 ecomzone-v3.1.zip create mode 100644 ecomzone-v5.1.zip create mode 100644 ecomzone-v5.zip create mode 100644 ecomzone/classes/EcomZoneClient.php create mode 100644 ecomzone/classes/EcomZoneCronTask.php create mode 100644 ecomzone/classes/EcomZoneLogger.php create mode 100644 ecomzone/classes/EcomZoneOrderSync.php create mode 100644 ecomzone/classes/EcomZoneProductSync.php create mode 100644 ecomzone/cron.php create mode 100644 ecomzone/ecomzone.php create mode 100644 ecomzone/views/templates/admin/configure.tpl diff --git a/ecomzone-v3.1.zip b/ecomzone-v3.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..fd616efe66f6acb0045461996c24595b35cff6fd GIT binary patch literal 10513 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 H3;6#4O1m7+ literal 0 HcmV?d00001 diff --git a/ecomzone-v5.zip b/ecomzone-v5.zip new file mode 100644 index 0000000000000000000000000000000000000000..7f63e14de58b22274d5be7f848c8d43ef1dc06ec GIT binary patch literal 10674 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 Ee{%%PBLDyZ literal 0 HcmV?d00001 diff --git a/ecomzone/classes/EcomZoneClient.php b/ecomzone/classes/EcomZoneClient.php new file mode 100644 index 0000000..fe3d14f --- /dev/null +++ b/ecomzone/classes/EcomZoneClient.php @@ -0,0 +1,107 @@ +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); + } +} diff --git a/ecomzone/classes/EcomZoneCronTask.php b/ecomzone/classes/EcomZoneCronTask.php new file mode 100644 index 0000000..1efff7a --- /dev/null +++ b/ecomzone/classes/EcomZoneCronTask.php @@ -0,0 +1,40 @@ +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; + } + } +} diff --git a/ecomzone/classes/EcomZoneLogger.php b/ecomzone/classes/EcomZoneLogger.php new file mode 100644 index 0000000..67e6db9 --- /dev/null +++ b/ecomzone/classes/EcomZoneLogger.php @@ -0,0 +1,25 @@ +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; + } +} \ No newline at end of file diff --git a/ecomzone/classes/EcomZoneProductSync.php b/ecomzone/classes/EcomZoneProductSync.php new file mode 100644 index 0000000..4ce10f6 --- /dev/null +++ b/ecomzone/classes/EcomZoneProductSync.php @@ -0,0 +1,270 @@ +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); + } +} +} diff --git a/ecomzone/cron.php b/ecomzone/cron.php new file mode 100644 index 0000000..fc8a4fa --- /dev/null +++ b/ecomzone/cron.php @@ -0,0 +1,43 @@ +runCronTasks(); + echo json_encode(['success' => true, 'result' => $result]); +} catch (Exception $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} \ No newline at end of file diff --git a/ecomzone/ecomzone.php b/ecomzone/ecomzone.php new file mode 100644 index 0000000..e67cb82 --- /dev/null +++ b/ecomzone/ecomzone.php @@ -0,0 +1,219 @@ +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('importPoducts')) { + 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; + } + } +} diff --git a/ecomzone/views/templates/admin/configure.tpl b/ecomzone/views/templates/admin/configure.tpl new file mode 100644 index 0000000..fc907eb --- /dev/null +++ b/ecomzone/views/templates/admin/configure.tpl @@ -0,0 +1,90 @@ +
+
+
+ {l s='EcomZone Configuration' mod='ecomzone'} +
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+ {l s='Manual Actions' mod='ecomzone'} +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ {l s='Cron Setup Instructions' mod='ecomzone'} +
+
+

{l s='Add the following command to your server crontab to run every hour:' mod='ecomzone'}

+
0 * * * * curl -s "{$shop_url}modules/ecomzone/cron.php?token={$ECOMZONE_CRON_TOKEN}"
+

{l s='Or using PHP CLI:' mod='ecomzone'}

+
0 * * * * php {$shop_root}modules/ecomzone/cron.php --token={$ECOMZONE_CRON_TOKEN}
+
+
+ +
+
+ {l s='Debug Information' mod='ecomzone'} +
+
+
+ + + {foreach from=$ECOMZONE_DEBUG_INFO key=key item=value} + + + + + {/foreach} + + + + + + + + + +
{$key|escape:'html':'UTF-8'}{$value|escape:'html':'UTF-8'}
{l s='Last Cron Run' mod='ecomzone'}{$ECOMZONE_LAST_CRON_RUN|escape:'html':'UTF-8'}
{l s='Next Scheduled Run' mod='ecomzone'}{$ECOMZONE_NEXT_CRON_RUN|escape:'html':'UTF-8'}
+
+
+
+ +
+
+ {l s='Recent Logs' mod='ecomzone'} +
+
+
+ {foreach from=$ECOMZONE_LOGS item=log} +
{$log|escape:'html':'UTF-8'}
+ {/foreach} +
+
+
\ No newline at end of file