Доповнення (розширення)

Створення власних доповнень#

ocStore підтримує доповнення (розширення), які додають або змінюють функціональність без редагування файлів ядра. Доповненнями можуть бути модулі (наприклад, слайдери чи аналітика), платіжні шлюзи, способи доставки, теми, звіти тощо. В ocStore 3 доповнення побудовані за шаблоном MVC-L (Model-View-Controller-Language), використовують систему подій (Events) як хуки та OCMOD для модифікацій. У цьому посібнику крок за кроком описано, як створити, запакувати й установити доповнення на прикладі модуля — найпоширенішого типу.

Що потрібно#

  • Робоча інсталяція ocStore 3.0.4.1.
  • Базові знання PHP, HTML, CSS, JavaScript і шаблону MVC.
  • Знайомство з шаблонізатором Twig (використовується для представлень).
  • Доступ до адмінки й файлової системи (через FTP або на сервері).
  • Редактор коду (наприклад, VS Code, Notepad++) та ZIP-архіватор.

Дотримуйтеся правил найменування: пакет для встановлення має бути ZIP-архівом із назвою, що закінчується на .ocmod.zip. Розмір файлу обмежено налаштуваннями PHP сервера (upload_max_filesize, post_max_size).

Типи доповнень#

Можливі типи доповнень:

  • Модулі: банери, каруселі, додаткові блоки тощо.
  • Канали просування: товарні фіди (наприклад, Google Merchant), карта сайту.
  • Оплата / Доставка: інтеграція платіжних шлюзів (наприклад, PayPal) або служб доставки.
  • Шаблони (теми): оформлення вітрини.
  • Звіти / Аналітикс: звіти в адмінці або інтеграція з сервісами на кшталт Google Analytics.
  • Інші: антифрод, капча, враховувати в замовленні (order totals), валюти, панель стану тощо.

Усі доповнення керуються в адмінці в розділі Доповнення → Доповнення (тип вибирається у списку «Оберіть тип доповнення»).

Структура каталогів#

В ocStore 3 файли доповнення розміщуються безпосередньо у стандартних каталогах ядра, у підтеці extension/ відповідного типу. Наприклад, для модуля test_module:

  • admin/ — файли адмінки:
    • controller/extension/module/test_module.php — контролер адмінки.
    • language/uk-ua/extension/module/test_module.php — мовні рядки (а також en-gb/... та інші мови).
    • view/template/extension/module/test_module.twig — представлення адмінки.
    • model/extension/module/test_module.php — необов'язкова модель для власних операцій із БД.
  • catalog/ — файли вітрини:
    • controller/extension/module/test_module.php — контролер вітрини.
    • language/uk-ua/extension/module/test_module.php — мовні рядки.
    • view/theme/default/template/extension/module/test_module.twig — представлення вітрини (default або назва вашої теми).
    • model/extension/module/test_module.php — необов'язкова модель.

Для встановлення через адмінку ці файли пакуються в архів test_module.ocmod.zip такої структури:

  • upload/ — файли, які буде скопійовано в корінь магазину зі збереженням шляхів (upload/admin/..., upload/catalog/...).
  • install.xml — необов'язковий OCMOD-модифікатор.

Стандартні доповнення розташовані в тих самих каталогах (admin/controller/extension/module/ тощо). Використовуйте унікальну назву модуля (наприклад, з префіксом вашого бренду), щоб уникнути конфліктів з іншими доповненнями.

Покроково: створення простого модуля#

Створимо демонстраційний модуль «Test Module», який за допомогою події виконує дію під час додавання товару в кошик.

Створіть теку#

Створіть теку test_module/, а в ній — підтеку upload/. Усередині upload/ створіть файли, описані нижче (шляхи вказано відносно upload/).

1. Контролер адмінки#

Файл: admin/controller/extension/module/test_module.php

Він відповідає за сторінку налаштувань в адмінці, збереження форми та реєстрацію події під час встановлення.

<?php
class ControllerExtensionModuleTestModule extends Controller {
    private $error = array();

    public function index() {
        $this->load->language('extension/module/test_module');

        $this->document->setTitle($this->language->get('heading_title'));

        $this->load->model('setting/setting');

        if (($this->request->server['REQUEST_METHOD'] == 'POST') && $this->validate()) {
            $this->model_setting_setting->editSetting('module_test_module', $this->request->post);

            $this->session->data['success'] = $this->language->get('text_success');

            $this->response->redirect($this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true));
        }

        if (isset($this->error['warning'])) {
            $data['error_warning'] = $this->error['warning'];
        } else {
            $data['error_warning'] = '';
        }

        $data['breadcrumbs'] = array();

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('text_home'),
            'href' => $this->url->link('common/dashboard', 'user_token=' . $this->session->data['user_token'], true)
        );

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('text_extension'),
            'href' => $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true)
        );

        $data['breadcrumbs'][] = array(
            'text' => $this->language->get('heading_title'),
            'href' => $this->url->link('extension/module/test_module', 'user_token=' . $this->session->data['user_token'], true)
        );

        $data['action'] = $this->url->link('extension/module/test_module', 'user_token=' . $this->session->data['user_token'], true);
        $data['cancel'] = $this->url->link('marketplace/extension', 'user_token=' . $this->session->data['user_token'] . '&type=module', true);

        if (isset($this->request->post['module_test_module_status'])) {
            $data['module_test_module_status'] = $this->request->post['module_test_module_status'];
        } else {
            $data['module_test_module_status'] = $this->config->get('module_test_module_status');
        }

        $data['header'] = $this->load->controller('common/header');
        $data['column_left'] = $this->load->controller('common/column_left');
        $data['footer'] = $this->load->controller('common/footer');

        $this->response->setOutput($this->load->view('extension/module/test_module', $data));
    }

    protected function validate() {
        if (!$this->user->hasPermission('modify', 'extension/module/test_module')) {
            $this->error['warning'] = $this->language->get('error_permission');
        }

        return !$this->error;
    }

    public function install() {
        // Завантажуємо модель подій
        $this->load->model('setting/event');

        // Реєструємо подію: code, trigger, action, status, sort_order
        $this->model_setting_event->addEvent(
            'test_module_cart_add_before',                        // Код події (унікальний ідентифікатор)
            'catalog/controller/checkout/cart/add/before',        // Тригер події
            'extension/module/test_module/onCartAddBefore',       // Метод-обробник
            1,
            1
        );
    }

    public function uninstall() {
        // Видаляємо подію під час деінсталяції
        $this->load->model('setting/event');
        $this->model_setting_event->deleteEventByCode('test_module_cart_add_before');
    }
}

2. Мовний файл адмінки#

Файл: admin/language/uk-ua/extension/module/test_module.php (за потреби продублюйте англійською в admin/language/en-gb/extension/module/test_module.php)

<?php
$_['heading_title']    = 'Test module';
$_['text_extension']   = 'Доповнення';
$_['text_success']     = 'Налаштування модуля змінено!';
$_['text_edit']        = 'Редагування модуля';
$_['entry_status']     = 'Статус';
$_['error_permission'] = 'У вас недостатньо прав для зміни модуля Test module!';

3. Шаблон адмінки#

Файл: admin/view/template/extension/module/test_module.twig

Адмінка ocStore 3 використовує Bootstrap 3 і Font Awesome 4.

{{ header }}{{ column_left }}
<div id="content">
  <div class="page-header">
    <div class="container-fluid">
      <div class="pull-right">
        <button type="submit" form="form-module" data-toggle="tooltip" title="{{ button_save }}" class="btn btn-primary"><i class="fa fa-save"></i></button>
        <a href="{{ cancel }}" data-toggle="tooltip" title="{{ button_cancel }}" class="btn btn-default"><i class="fa fa-reply"></i></a></div>
      <h1>{{ heading_title }}</h1>
      <ul class="breadcrumb">
        {% for breadcrumb in breadcrumbs %}
        <li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
        {% endfor %}
      </ul>
    </div>
  </div>
  <div class="container-fluid">
    {% if error_warning %}
    <div class="alert alert-danger alert-dismissible"><i class="fa fa-exclamation-circle"></i> {{ error_warning }}
      <button type="button" class="close" data-dismiss="alert">&times;</button>
    </div>
    {% endif %}
    <div class="panel panel-default">
      <div class="panel-heading">
        <h3 class="panel-title"><i class="fa fa-pencil"></i> {{ text_edit }}</h3>
      </div>
      <div class="panel-body">
        <form action="{{ action }}" method="post" enctype="multipart/form-data" id="form-module" class="form-horizontal">
          <div class="form-group">
            <label class="col-sm-2 control-label" for="input-status">{{ entry_status }}</label>
            <div class="col-sm-10">
              <select name="module_test_module_status" id="input-status" class="form-control">
                {% if module_test_module_status %}
                <option value="1" selected="selected">{{ text_enabled }}</option>
                <option value="0">{{ text_disabled }}</option>
                {% else %}
                <option value="1">{{ text_enabled }}</option>
                <option value="0" selected="selected">{{ text_disabled }}</option>
                {% endif %}
              </select>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
</div>
{{ footer }}

4. Контролер подій на вітрині#

Файл: catalog/controller/extension/module/test_module.php

Він обробляє подію й записує рядок у журнал, коли вона спрацьовує.

Обробник події before отримує $route і $args за посиланням, тож може змінити аргументи виклику. Обробник події after додатково отримує $output — його можна змінити, щоб вплинути на кінцевий результат.

<?php
class ControllerExtensionModuleTestModule extends Controller {

    public function onCartAddBefore(&$route, &$args) {
        // Тут виконуйте свої дії
        $this->log->write('onCartAddBefore() has been successfully triggered!');
    }
}

5. Необов'язково: моделі#

Для власних операцій із базою даних створіть admin/model/extension/module/test_module.php або catalog/model/extension/module/test_module.php з класом ModelExtensionModuleTestModule extends Model і методами на кшталт addData() із SQL-запитами.

6. OCMOD для змін у ядрі (якщо подій недостатньо)#

Для змін, які не можна зробити через події, використовуйте OCMOD — XML-файл install.xml у корені архіву (поруч із текою upload/). Приклад додавання пункту меню в адмінку:

<?xml version="1.0" encoding="utf-8"?>
<modification>
    <name>Example OCMOD</name>
    <code>example_ocmod</code>
    <version>1.0</version>
    <author>Your Name</author>
    <link>https://yourwebsite.com</link>
    <file path="admin/controller/common/column_left.php">
        <operation>
            <search><![CDATA[if ($marketplace) {]]></search>
            <add position="before"><![CDATA[
                $data['menus'][] = array(
                    'id'       => 'test-module',
                    'icon'     => 'fa-cog',
                    'name'     => 'Test Module',
                    'href'     => $this->url->link('extension/module/test_module', 'user_token=' . $this->session->data['user_token'], true),
                    'children' => array()
                );
            ]]></add>
        </operation>
    </file>
</modification>

Пакування та встановлення#

  1. Запакуйте вміст теки доповнення в ZIP з назвою на кшталт test_module.ocmod.zip. Пакувати потрібно не саму теку test_module/, а її вміст: відкривши архів, ви маєте одразу побачити upload/ (і install.xml, якщо він є).
  2. Перейдіть у Доповнення → Установка доповнень і завантажте ZIP-файл. Установка запуститься автоматично, хід процесу відображається на сторінці, а пакет з'явиться в «Історії установок».
  3. Перейдіть у Доповнення → Доповнення, виберіть тип Модулі і натисніть «Активація» (зелена кнопка «+») навпроти вашого модуля. Під час цього виконується метод install().
  4. Якщо пакет містить OCMOD, оновіть кеш модифікацій у Доповнення → Модифікатори → кнопка «Оновити» (синя).
  5. За потреби очистіть кеш: Панель стану → кнопка з шестернею («Налаштування розробника»).
  6. Налаштуйте й увімкніть модуль у Доповнення → Доповнення → Модулі (кнопка «Редагувати»).

Якщо виникають помилки, перегляньте журнали в system/storage/logs/ (або Система → Обслуговування → Журнал помилок) і переконайтеся у відсутності конфліктів файлів.

Рекомендації#

  • Найменування класів: назва класу має відповідати шляху (admin/controller/extension/module/test_module.php → ControllerExtensionModuleTestModule).
  • Інтернаціоналізація: увесь текст виносьте в мовні файли (щонайменше uk-ua та en-gb).
  • Безпека: перевіряйте вхідні дані та права доступу ($this->user->hasPermission()).
  • Сумісність: тестуйте на різних темах і версіях PHP (ocStore 3.0.4.1 підтримує PHP 8).
  • Прибирання: реалізуйте uninstall(), щоб видаляти записи в БД і події.
  • Документація: додайте інструкції в README у пакеті.
  • Мінімум змін у ядрі: коли можливо, віддавайте перевагу подіям, а не OCMOD.
  • Тестування: працюйте на тестовому магазині, частіше оновлюйте кеш і модифікатори.
  • Публікація: якщо поширюєте доповнення, дотримуйтеся вимог маркетплейсу, де його розміщуєте.

Більше прикладів#

Наведений приклад базовий і допомагає зрозуміти логіку створення доповнень. Щоб побачити, як улаштовані доповнення всіх типів (модулі, платіжні шлюзи, способи доставки тощо), найкраще вивчити стандартні доповнення, що входять до складу ocStore: їхні файли лежать у admin/controller/extension/<тип>/, catalog/controller/extension/<тип>/ і відповідних каталогах мов, моделей та шаблонів (наприклад, модулі bestseller, latest, carousel, способи оплати bank_transfer, cod). Скопіюйте структуру й адаптуйте її під свої потреби.

Сторінку перекладено й адаптовано для ocStore з офіційної документації OpenCart. Ліцензія — GNU GPL v3.