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

Автор этой записи несколько лет назад сам искал информацию по этой теме и скажу, что материала достаточно не много. 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.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *