Как написать свой модуль в OpenCart? Часть 1

ecommerce
18 мин. на чтение

Автор этой записи несколько лет назад сам искал информацию по этой теме и скажу, что материала достаточно не много. OpenCart только начал обрастать атрибутами серьезного проекта. В моем понимании это автоматизированные тесты и документация. Если у вас со знанием английского есть небольшие сложности, можно поискать и найти документацию для разработчиков на русском языке, за что ребятам большое спасибо. Кстати, проект открыт и вы тоже можете присоединиться к описанию классов и файлов, если есть свободное время и желание.

Сегодня мы напишем простой модуль для OpenCart 2.3 – самой свежей на момент написания статьи версии этого движка интернет магазина.

В качестве примера мы создадим модуль Яндекс.Метрики с админ частью по аналогии с предустановленным модулем Google Analytics. Несмотря на свою продвинутую мультиязычность, комьюнити OpenCart не особо заморачивается с поддержкой различных систем сбора аналитики (что-то кроме Google Analytics), хотя их существует множество – Яндекс.Метрика,Piwik, Adobe Marketing Cloud, Kissmetrics, Open Web Analytics и многое другое. Лично я считаю это недочетом. Давайте исправлять.

Итак, сейчас в разделе Модули->Расширения->Аналитика вы увидите следующую картину:

Мы будем добавлять новый модуль Яндекс.Метрика, взяв за основу существующий – Google Analytics. Т.е. после наших действий этот экран должен выглядеть так:

Не удивляйтесь почему модули оформлены Разделами. Дело в том, что OpenCart отлично поддерживает мультимагазин, т.е. возможность размещения на одном сервере и бекенде нескольких магазино с различными товарами, оформление и конечно же модулями (расширениями). Соответственно для каждого подмагазина (возможно) понадобится свой код счетчика.

Начинаем

Наш будущий модуль имеет сразу две части – публичную в качестве добавления кода счетчика Яндекс.Метрики на страницы и административную, где модуль можно включать-выключать, а так же вносить изменения в код счетчика. Начнем с административной части.

Административная часть модуля

Любой модуль OpenCart начинается с контроллера, т.к. движок написан на паттерне MVC. Поэтому в папке admin/controller/extension/analytics создайте новый файл yandex_metrica.php
Должно получиться так:

Теперь, начнем этот файл наполнять логикой, беря за основу находящийся уже в этой папке контроллер google_analytics.php.

Я всегда советую начинать писать модули с копирования существующих и переделывания их под себя, т.к. документация по OpenCart, особенно в части написания модулей оставляет желать лучшего.

Объявим новый класс ControllerExtensionAnalyticsYandexMetrica как дочерний глобального класса Controller, а также используя модификатор области видимости private объявим переменную в виде массива, в который будут записываться ошибки. Эта строка обязательная для корректной отладки своего модуля, а так же логирования ошибок PHP в служебный файл лога.

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

Далее создадим функцию index(). Эта функция исполняется при попытке прямого обращения к нашему модулю через GET запрос index.php?route=extension/analytics/yandex_metrica

public function index() {

Первым делом создадим файл локализации yandex_metrica.php для нашего модуля в папке /admin/language/ru-ru/extension/analytics. Все конечно понимают, что в данном случае мы создаем русскую локализацию модуля. Если вы хотите создать для английской версии, то путь немного отличается /admin/language/en-gb/extension/analytics, эта локализация кстати базовая для OpenCart и он будет брать ее в случае отсутствия у него локализации для выбранного в настройках магазина языка.
Содержание этого файла:

<?php
 $_['heading_title'] = 'Яндекс.Метрика';
 // Text
 $_['text_extension'] = 'Расширения';
 $_['text_success'] = 'Настройки успешно изменены!';
 $_['text_signup'] = 'Войдите в аккаунт <a href="https://metrika.yandex.ru/list" target="_blank"><u>Яндекс.Метрику</u></a> и после создания профиля сайта, скопируйте, а затем вставьте код в данное поле.';
 $_['text_default'] = 'По умолчанию';
 $_['text_edit'] = 'Настройки модуля';
 // Entry
 $_['entry_code'] = 'Код Яндекс.Метрики';
 $_['entry_status'] = 'Статус';
 // Error
 $_['error_permission'] = 'У Вас нет прав для управления данным модулем!';
 $_['error_code'] = 'Код необходим!';

Теперь мы можем вернуться к коду контроллеру и подключить этот файл локализации в наш контроллер /admin/controller/extension/analytics/yandex_metrica.php. Так же присвоим некоторым переменным значения.

// Подключим файл локализации
$this->load->language('extension/analytics/yandex_metrica');
//Зададим заголовок страницы, мета-тег title из файла локализации
$this->document->setTitle($this->language->get('heading_title'));
// Зададим название модуля в списке модулей
$data['heading_title'] = $this->language->get('heading_title');
// Текст кнопки Редактировать
$data['text_edit'] = $this->language->get('text_edit');
// Текст "Включено"
$data['text_enabled'] = $this->language->get('text_enabled');
// Текст "Выключено"
$data['text_disabled'] = $this->language->get('text_disabled');
// Текст "Установить"
$data['text_signup'] = $this->language->get('text_signup');
// Текст метки поля "Код"		
$data['entry_code'] = $this->language->get('entry_code');
// Текст метки переключателя статуса модуля
$data['entry_status'] = $this->language->get('entry_status');
// Текст кнопки "Сохранить"
$data['button_save'] = $this->language->get('button_save');
// Текст кнопки "Отменить"
$data['button_cancel'] = $this->language->get('button_cancel');

Теперь добавим немного логики. Подключим Модель для работы с глобальной таблицей настроек OpenCart – setting, зададим логику обработки предупреждений и ошибок, а так же напишем что будет происходить при нажатии кнопки “Сохранить”.

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

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

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

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

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

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

Логика написанного достаточно проста. Сначала мы подключаем модель для работы с таблицей глобальных настроек OpenCart $this->load->model('setting/setting');, дальше с помощью конструкции if мы задали условие, что если есть запрос методом POST и он прошел валидацию, то мы сохраняем полученные данные в БД с помощью функции модели editSetting. Обратите внимание, что функция получили в качестве параметров кроме прочего и значение store_id. Как я говорил выше, OpenCart поддерживает мультимагазин и параметр store_id позволяет разделять настройки для разных подмагазинов.

Дальше, если валидация не прошла (т.е. перемеменные $this->error['code'] и $this->error['warning'] не пустые) данные не будут сохранены, а текст ошибки выведется на странице модулей.

Давайте создадим хлебные крошки, которые потом выведем в Представлении, у нас они 3-го уровня вложенности: Главная страница админки -> Модули -> Яндекс.Метрика

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

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

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

$data['breadcrumbs'][] = array(
'text' => $this->language->get('heading_title'),
'href' => $this->url->link('extension/analytics/yandex_metrica', 'token=' . $this->session->data['token'] . '&store_id=' . $this->request->get['store_id'], true)
);

Обратите внимание, что с точки зрения разработчиков OpenCart хлебные крошки нужно хранить в многомерном массиве, который потом будет выводиться в цикле foreaсh. Ничего против такого подхода не имею.
Обращаю внимание начинающих разработчиков OpenCart на булевое значение переменной true в значении переменной массива $data['breadcrumbs']['href']. Тот, кто уже знаком с более ранними версиями OpenCart безусловно знает, что раньше там было значние SSL, например, как бы этот код выглядел в OpenCart 1.5.6.4:

$this->data['breadcrumbs'][] = array(
			'text'      => $this->language->get('heading_title'),
			'href'      => $this->url->link('extension/payment', 'token=' . $this->session->data['token'], 'SSL'),
			'separator' => ' :: '
		);

Значние константы SSL, а теперь уже булевой переменной true определяет префикс http(s):// в URL Представления. Если у вас в настройках файла config.php указаны значения констант для путей с https://

< ?php
// HTTP
define('HTTP_SERVER', 'http://yourdomain.tdl/admin/');
define('HTTP_CATALOG', 'http://yourdomain.tdl/');

// HTTPS
define('HTTPS_SERVER', 'https://yourdomain.tdl/admin/');
define('HTTPS_CATALOG', 'https://yourdomain.tdl/');

Так же для полноценной работы HTTPS необходимо настроить соответствующий параметр в административной части:

Однозначно возникает вопрос, зачем так заумно решать эту задачу с HTTPS? Например, если у нас включен HTTPS для магазина, а ссылка, которую вы хотите поставить должна вести на обычный http://… Сценариев, где это может понадобиться я пока не встречал, но смею предположить, что речь идет о внешних ссылках, т.к. HTTPS зачастую включают для всего сайта целиком – административной и пользовательской части. Автор статьи настоятельно рекомендует использовать именно такой способ формирования ссылок: из Контроллера в Представление через переменную $this->url->link, т.к. в этом случае они корректно работают с ЧПУ OpenCart.

Продолжим писать код в наш контроллер. Добавим код, который будет отвечать за код ссылки кнопок Редактирования и Отмены в списке подмагазинов и определим получения токена. Токен нужен для сессии, т.к. административная часть защищена паролем.

$data['action'] = $this->url->link('extension/analytics/yandex_metrica', 'token=' . $this->session->data['token'] . '&store_id=' . $this->request->get['store_id'], true);

		$data['cancel'] = $this->url->link('extension/extension', 'token=' . $this->session->data['token'] . '&type=analytics', true);
		
		$data['token'] = $this->session->data['token'];

Напишем два условия на значение полей – поля для самого кода Яндекс.Метрики и переключателя статуса Включено-Выключено.

if (isset($this->request->post['yandex_metrica_code'])) {
			$data['yandex_metrica_code'] = $this->request->post['yandex_metrica_code'];
		} else {
			$data['yandex_metrica_code'] = $this->model_setting_setting->getSettingValue('yandex_metrica_code', $this->request->get['store_id']);
		}
		
		if (isset($this->request->post['yandex_metrica_status'])) {
			$data['yandex_metrica_status'] = $this->request->post['yandex_metrica_status'];
		} else {
			$data['yandex_metrica_status'] = $this->model_setting_setting->getSettingValue('yandex_metrica_status', $this->request->get['store_id']);
		}

Если есть POST запрос, то содержание переменной кода берется из значения поля формы в Представлении и передается в Контроллер, в противном случае Контроллер передает в Представление значение, которое записано в БД и получено от Модели. Аналогично происходит и с полем Статус.

Осталось дописано всего несколько строк.

$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/analytics/yandex_metrica', $data));
	}

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

Осталось написать еще одну функцию validate() в нашем классе модуля и можно считать, что с контроллером мы расправились.

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

		if (!$this->request->post['yandex_metrica_code']) {
			$this->error['code'] = $this->language->get('error_code');
		}			

		return !$this->error;
	}
}

Все конечно же понимают, что в данной функции мы проверям наличие прав на редактирование модуля, а также не попытался ли пользователь передать в Контроллер из Представления пустое поле Код. Теоретически у нас может и отсутствовать код, но для этого есть поле Статус, которым можно отключить отображение кода счетчика в клиентской части, тем самым оптимизировав вывод интерпретатором PHP кода.
Мы сэкономили на кодировке Модели для этого модуля, но т.к. Представление будет передавать нам Поля HTML с другими именами, его все таки придется создать, чем мы и займемся в следующей части этой статьи.

Игорь Чишкала
Директор по технологиям в SoftForge. Люблю ИТ, пишу технические статьи в этом блоге или для сайта фриланс-биржи Upwork. Кодю на PHP с использованием фреймворков Laravel или Symfony.
Оцените автора
Авторский блог Игоря Чишкалы
Добавить комментарий для Игорь Чишкала Отменить ответ

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.

  1. Павел

    В третьей версии тот же путь? Или что-то новое?

    Ответить
    1. Игорь Чишкала автор

      Twig, а так все очень похоже.

      Ответить