Розробка кастомного модуля OpenCart
Стандартний функціонал OpenCart покриває більшість базових e-commerce завдань, але рано чи пізно виникають вимоги, які виходять за рамки налаштувань: інтеграція з конкретною обліковою системою, нестандартна логіка ціноутворення, специфічна форма замовлення, кастомний блок на вітрині. Це завдання для розширень (extension) — основного механізму розширення OpenCart.
Типи розширень OpenCart
OpenCart 4.x структурує розширення за типами:
| Тип | Назначення |
|---|---|
module |
Блоки на вітрині (баннери, хіти, виборки товарів) |
payment |
Платіжні шлюзи |
shipping |
Методи доставки з розрахунком вартості |
total |
Позиції в сумарній вартості замовлення (знижки, збори, податки) |
report |
Звіти в панелі адміністратора |
event |
Обробники подій системи (гаки) |
theme |
Теми оформлення |
language |
Мовні пакети |
Більшість кастомних завдань вирішується через module (вітрина) або event (інтеграції, модифікатори бізнес-логіки).
Структура кастомного модуля
Модуль розташовується в папці extension/{vendor_name}/:
extension/
└── myvendor/
├── admin/
│ ├── controller/
│ │ └── module/
│ │ └── mymodule.php
│ ├── language/
│ │ ├── en-gb/module/mymodule.php
│ │ └── ru-ru/module/mymodule.php
│ ├── model/
│ │ └── module/
│ │ └── mymodule.php
│ └── view/
│ └── template/module/
│ └── mymodule.twig
└── catalog/
├── controller/
│ └── module/
│ └── mymodule.php
├── language/
│ ├── en-gb/module/mymodule.php
│ └── ru-ru/module/mymodule.php
├── model/
│ └── module/
│ └── mymodule.php
└── view/
└── template/module/
└── mymodule.twig
Приклад: модуль «Товари з тієї ж категорії»
Практичний приклад — модуль, що показує товари з тієї ж категорії, що й переглядаємий товар.
Admin Controller (admin/controller/module/related_category.php):
<?php
namespace Opencart\Admin\Controller\Extension\Myvendor\Module;
class RelatedCategory extends \Opencart\System\Engine\Controller
{
public function index(): void
{
$this->load->language('extension/myvendor/module/related_category');
$this->document->setTitle($this->language->get('heading_title'));
$data['heading_title'] = $this->language->get('heading_title');
// Налаштування модуля з форми
$data['limit'] = $this->config->get('module_related_category_limit') ?: 6;
$data['status'] = $this->config->get('module_related_category_status');
$data['action'] = $this->url->link(
'extension/myvendor/module/related_category.save',
'user_token=' . $this->session->data['user_token']
);
$this->response->setOutput($this->load->view(
'extension/myvendor/module/related_category',
$data
));
}
public function save(): void
{
$this->load->language('extension/myvendor/module/related_category');
if ($this->request->server['REQUEST_METHOD'] == 'POST') {
$this->load->model('setting/setting');
$this->model_setting_setting->editSetting('module_related_category', [
'module_related_category_status' => $this->request->post['status'],
'module_related_category_limit' => (int) $this->request->post['limit'],
]);
$this->response->redirect($this->url->link(
'marketplace/extension',
'user_token=' . $this->session->data['user_token'] . '&type=module'
));
}
}
}
Catalog Controller (catalog/controller/module/related_category.php):
<?php
namespace Opencart\Catalog\Controller\Extension\Myvendor\Module;
class RelatedCategory extends \Opencart\System\Engine\Controller
{
public function index(array $setting): string
{
// Працює тільки на сторінці товара
if ($this->router->getPath() !== 'product/product') {
return '';
}
$product_id = (int) ($this->request->get['product_id'] ?? 0);
if (!$product_id) {
return '';
}
$this->load->model('extension/myvendor/module/related_category');
$this->load->model('tool/image');
$limit = $setting['limit'] ?? 6;
$products = $this->model_extension_myvendor_module_related_category
->getProductsFromSameCategory($product_id, $limit);
if (!$products) {
return '';
}
$data['products'] = [];
foreach ($products as $product) {
$data['products'][] = [
'product_id' => $product['product_id'],
'name' => $product['name'],
'href' => $this->url->link('product/product', 'product_id=' . $product['product_id']),
'thumb' => $this->model_tool_image->resize(
$product['image'],
$this->config->get('config_image_related_width'),
$this->config->get('config_image_related_height')
),
'price' => $this->currency->format(
$this->tax->calculate($product['price'], $product['tax_class_id'], true),
$this->session->data['currency']
),
];
}
return $this->load->view(
'extension/myvendor/module/related_category',
$data
);
}
}
Catalog Model:
<?php
namespace Opencart\Catalog\Model\Extension\Myvendor\Module;
class RelatedCategory extends \Opencart\System\Engine\Model
{
public function getProductsFromSameCategory(int $productId, int $limit): array
{
$query = $this->db->query("
SELECT DISTINCT p.product_id, pd.name, p.image, p.price, p.tax_class_id
FROM oc_product p
JOIN oc_product_description pd
ON p.product_id = pd.product_id AND pd.language_id = '" . (int) $this->config->get('config_language_id') . "'
JOIN oc_product_to_category ptc
ON p.product_id = ptc.product_id
WHERE ptc.category_id IN (
SELECT category_id FROM oc_product_to_category WHERE product_id = '" . $productId . "'
)
AND p.product_id != '" . $productId . "'
AND p.status = 1
AND p.date_available <= NOW()
ORDER BY RAND()
LIMIT " . $limit . "
");
return $query->rows;
}
}
Система подій (Events)
Для модифікації існуючої поведінки без правки ядра — події:
// Реєстрація події в методі встановлення модуля:
$this->load->model('setting/event');
$this->model_setting_event->addEvent([
'code' => 'myvendor_related_category',
'description' => 'Add category data to product page',
'trigger' => 'catalog/controller/product/product/before',
'action' => 'extension/myvendor/event/product.before',
'status' => true,
'sort_order' => 0,
]);
Обробник події:
// catalog/controller/event/product.php
class Product extends \Opencart\System\Engine\Controller
{
public function before(\Opencart\System\Engine\Action &$route, array &$args, mixed &$output): void
{
// Включається до завантаження контролера product/product
// Можна додати дані в $this->registry або модифікувати $args
}
public function after(\Opencart\System\Engine\Action &$route, array &$args, mixed &$output): void
{
// Включається після генерації HTML сторінки товара
// Тут можна інжектувати HTML-блоки в $output через str_replace
$output = str_replace(
'<!-- related_products_placeholder -->',
$this->getRelatedCategoryHtml(),
$output
);
}
}
Приклад: модуль інтеграції з 1С (webhook)
Приймання даних від 1С для оновлення остатків:
// catalog/controller/api/stock_update.php
class StockUpdate extends \Opencart\System\Engine\Controller
{
public function index(): void
{
// Перевірка API-ключа
$apiKey = $this->request->server['HTTP_X_API_KEY'] ?? '';
if ($apiKey !== $this->config->get('module_1c_api_key')) {
$this->response->addHeader('HTTP/1.0 403 Forbidden');
$this->response->setOutput(json_encode(['error' => 'Unauthorized']));
return;
}
$payload = json_decode(file_get_contents('php://input'), true);
$this->load->model('catalog/product');
$updated = 0;
foreach ($payload['items'] ?? [] as $item) {
$products = $this->model_catalog_product->getProductsByModel($item['sku']);
foreach ($products as $product) {
$this->model_catalog_product->editProduct($product['product_id'], [
'quantity' => max(0, (int) $item['quantity']),
]);
$updated++;
}
}
$this->response->addHeader('Content-Type: application/json');
$this->response->setOutput(json_encode(['updated' => $updated]));
}
}
Установник модуля (install/uninstall)
public function install(): void
{
// Створюємо таблиці якщо потрібні
$this->db->query("
CREATE TABLE IF NOT EXISTS `oc_mymodule_log` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`message` TEXT NOT NULL,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
");
// Реєструємо події
$this->load->model('setting/event');
// ...
// Налаштування за замовчуванням
$this->load->model('setting/setting');
$this->model_setting_setting->editSetting('module_mymodule', [
'module_mymodule_status' => 1,
'module_mymodule_limit' => 6,
]);
}
public function uninstall(): void
{
$this->db->query("DROP TABLE IF EXISTS `oc_mymodule_log`");
$this->load->model('setting/event');
$this->model_setting_event->deleteEventByCode('myvendor_mymodule');
}
Упаковка модуля для Extension Installer
Структура ZIP-архіву для завантаження через Extension Installer:
mymodule.ocmod.zip
├── extension/
│ └── myvendor/
│ ├── admin/...
│ └── catalog/...
└── install.json
install.json:
{
"name": "My Custom Module",
"version": "1.0.0",
"author": "My Company",
"link": "https://mycompany.ua"
}
Терміни розробки
- Простий витринний модуль (блок на сторінку): 1–2 дні
- Модуль з налаштуваннями в адмінці + вивід на вітрині: 2–3 дні
- Платіжний шлюз (новий провайдер): 3–5 днів
- Метод доставки з розрахунком через API: 2–4 дні
- Інтеграція з зовнішною системою (1С, ERP) через webhook: 3–5 днів
- Кастомна логіка ціноутворення (знижки, групи клієнтів): 3–5 днів







