Разработка кастомного модуля OpenCart

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного модуля OpenCart
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомного модуля 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)

Для модификации существующего поведения без правки ядра — события:

// Регистрация события в install-методе модуля:
$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.by"
}

Сроки разработки

  • Простой витринный модуль (блок на страницу): 1–2 дня
  • Модуль с настройками в админке + вывод на витрине: 2–3 дня
  • Платёжный шлюз (новый провайдер): 3–5 дней
  • Метод доставки с расчётом через API: 2–4 дня
  • Интеграция с внешней системой (1С, ERP) через webhook: 3–5 дней
  • Кастомная логика ценообразования (скидки, группы клиентов): 3–5 дней