Дополнительное поле в карточке товара OpenCart +6


Статья нашего разработчика из его личного блога.

Разрабатывая модуль для OpenCart, возникла необходимость сделать произвольное поле в карточке товара, которое должно быть видно только в админке. Это должно быть поле с логическим значением "маркирован товар или нет". Вот так в итоге:

Дополнительное поле в карточке товара OpenCart
Дополнительное поле в карточке товара OpenCart

Недолго поковыряв админку выяснил, что из коробки OpenCart не поддерживает кастомные поля, однако есть платные модули, например, вот. Значит нужно сделать кастомное поле в OpenCart самому.

Варианты решения задачи:

  • заюзать неиспользуемые поля (sku, upc, ean, jan, isbn, mpn) - почти мгновенно, но возможно кто-то из наших клиентов будет использовать занятое нами поле.

  • вмешаться в код движка - быстро и мало кода, однако тогда решение не переносимо и при обновлении движка придется обновлять и само решение, так как изменения будут потеряны при обновлении движка.

  • разработать модуль - дольше, больше кода, переносимо на другие сайты с той же версией OpenCart, не зависит от обновления движка (за исключением мажорных версий).

Для реализации дополнительного поля в карточке товара будем разрабатывать модуль ProductMarkedField. В общем схема выглядит так:

  • изменить таблицу базы данных при установке модуля и прибраться в ней после удаления нашего модуля.

  • показать дополнительное поле в карточке товара в админке OpenCart.

  • сохранить поле при отправке формы

Последние два пункта должны быть выполнены при подписке на события показа карточки товара и изменения товара.

Про детали разработки модулей для opencart поговорим в другой раз, а пока предположим что читатель имеет минимальный опыт в этом деле.

Основная логика модуля умещается в один файл контроллера  admin/controller/extension/module/productmarkedfield.php. Для того чтобы модуль отобразился в разделе "Расширений" (чтобы его можно было инсталлировать/деинсталлировать) надо создать языковой файл admin/language/ru-ru/extension/module/productmarkedfield.php с таким содержимым:

<?php $_['heading_title'] = 'Кастомное поле в карточке товара "Маркировка"';

Установка модуля

В методе install нужно модифицировать таблицу product: 

$this->db->query("ALTER TABLE `".DB_PREFIX."product` ADD `marked` TINYINT UNSIGNED NOT NULL DEFAULT '0';");

В ocStore 2.3.x все нормально, но в ocStore 3.0.2.0 при использовании MySQL 8, запрос добавления нового столбца в таблицу заканчивался ошибкой:

Для решения этой проблемы изменим значение по умолчанию для столбца date_available:

$this->db->query("ALTER TABLE `".DB_PREFIX."product` CHANGE `date_available` `date_available` DATE NOT NULL;");

Теперь надо добавить обработчики событий, чтобы с их помощью мы могли изменять верстку карточки товара и сохранять значение нашего дополнительного поля (для ocStore 2.3.x):

$this->load->model('extension/event');
 
//событие "после загрузки формы товара" - для показа дополнительного поля товара (обязательна маркировка или нет)
$this->model_extension_event->addEvent(
  'productmarkedfield', //код, в данном случае название модуля
  'admin/view/catalog/product_form/after', //событие 
  'extension/module/productmarkedfield/eventProductFormAfter' //обработчик
);
 
//событие "после редактирования товара" - для сохранения статуса маркировки
$this->model_extension_event->addEvent(
  'productmarkedfield', 
  'admin/model/catalog/product/editProduct/after', 
  'extension/module/productmarkedfield/eventProductEditAfter'
);

Для ocStore 3.0.x модель событий загружается таким образом:

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

И вместо объекта model_extension_event нужно использовать model_setting_event соответственно.

Дополнительное поле в карточке товара

Выводить наше поле в карточку товара мы будем после загрузки admin/view/template/catalog/product_form.twig. наш метод будет принимать 3 аргумента:

public function eventProductFormAfter(
&$route,
&$args, //переданные аргументы в этот шаблон
&$output//html верстка страницы
)

Нас интересует третий аргумент &$output, именно его мы и будем модифицировать вставляя туда верстку со значением нашего нового поля.

Для модификации формы нам понадобится Simple HTML DOM, и краткий мануал. Скачиваем и кладём его в system/library, а в коде подгружаем расположенный внутри класс таим образом (@ чтобы не выводить ошибки, так как оригинальная версия этой библиотеки загружается с некритичными ошибками):

@$this->load->library('simple_html_dom');

Затем используя регулярные выражения для того чтобы получить id товара. (Искал другие варианты, но id в нормальном виде в $args массиве не нашел):

preg_match("/product_id=(\d+)/", $args["action"], $aMatch);
$idProdict = $aMatch[1];

Загружаем модель описывающую товар и получаем необходимую информацию о товаре (данные из таблиц product и product_description):

$this->load->model('catalog/product');
$aProduct = $this->model_catalog_product->getProduct($idProdict);

Теперь мы имеем доступ к данным товара, среди которых наше дополнительное поле - значение маркировки.

Однако, этот код будет работать только для редактирования уже существующего товара, но при создании нового товара будут ошибки, так как не будет найден id еще несуществующего товара. Изменим вышеприведенный код:

$isMarked = false;
if(preg_match("/product_id=(\d+)/", $args["action"], $aMatch))
{
    $idProduct = $aMatch[1];
    $this->load->model('catalog/product');
    $aProduct = $this->model_catalog_product->getProduct($idProduct);
    $isMarked = $aProduct["marked"];
}

То есть мы создали новую переменную isMarked, которая будет хранить в себе значение по умолчанию false и если удастся найти id товара, тогда в isMarked будет записано значение из нашего произвольного поля в карточке товара.

Теперь при помощи Simple HTML DOM найдем вкладку "Данные" и вставим в самое начало наше поле маркировки, предварительно подсмотрев на верстку необходимого gui элемента в admin/view/template/catalog/product_form.twig (в ocStore 2.3.x tpl расширение, и внутри нет Twig):

$html = str_get_html($output);
$html->find('div#tab-data', 0)->innertext = 
'<div class="form-group">
    <label class="col-sm-2 control-label">Маркирован</label>
    <div class="col-sm-10">
        <label class="radio-inline">
            <input type="radio" name="marked" value="1" '.($aProduct["marked"] ? 'checked="checked"' : "").'>Да
        </label>
        <label class="radio-inline">
            <input type="radio" name="marked" value="0" '.(!$aProduct["marked"] ? 'checked="checked"' : "").'>Нет
        </label>
    </div>
</div>' . $html->find('div#tab-data', 0)->innertext;

Код рабочего метода целиком:

public function eventProductFormAfter(&$route, &$args, &$output)
{
    @$this->load->library('simple_html_dom');
    $isMarked = false;
    if(preg_match("/product_id=(\d+)/", $args["action"], $aMatch))
    {
        $idProduct = $aMatch[1];
        $this->load->model('catalog/product');
        $aProduct = $this->model_catalog_product->getProduct($idProduct);
        $isMarked = $aProduct["marked"];
    }
     
    $html = str_get_html($output);
    $html->find('div#tab-data', 0)->innertext = 
    '<div class="form-group">
        <label class="col-sm-2 control-label">Маркирован</label>
        <div class="col-sm-10">
            <label class="radio-inline">
                <input type="radio" name="marked" value="1" '.($isMarked ? 'checked="checked"' : "").'>Да
            </label>
            <label class="radio-inline">
                <input type="radio" name="marked" value="0" '.(!$isMarked ? 'checked="checked"' : "").'>Нет
            </label>
        </div>
    </div>' . $html->find('div#tab-data', 0)->innertext;
    $output = $html->outertext;
}

Сохранение значения дополнительного поля

Для сохранения результатов в момент когда администратор нажимает кнопку "Сохранить", необходимо вручную (с помощью обработчика события) внести изменения в базу данных, так как модель catalog/product при редактировании товара (ModelCatalogProduct::editProduct) сохраняет только определенный набор данных, и наше новое поле не входит в этот набор.

Для этого мы уже зарегистрировали ранее обработчик события "после редактирования товара":

public function eventProductEditAfter(&$route, &$args)
{
  //в $args[0] лежит id товара
  $sSql = "UPDATE " . DB_PREFIX . "product SET marked = " . $this->db->escape($args[1]['marked']) . " WHERE product_id = '" . (int)$args[0] . "'";
  $this->db->query($sSql);
}

Удаление модуля

При деинсталляции модуля надо удалить столбец marked из таблицы product и удалить обработчики событий, установленные нашим модулем. Все это делается в методе uninstall.

Удалить столбец из таблицы товаров:

$this->db->query("ALTER TABLE `".DB_PREFIX."product` DROP `marked`");

Удалить все сообщения (для ocStore 2.3.x):

$this->load->model('extension/event');
$this->model_extension_event->deleteEvent('productmarkedfield');

Удалить все сообщения (ocStore 3.0.x):

$this->load->model('setting/event');
$this->model_setting_event->deleteEventByCode('productmarkedfield');

Послесловие

В целом не так уж и трудно, однако кажется немного странным тот факт, что верстку надо менять руками, так как нет удобного встроенного инструмента для изменения интерфейса.

Для тех, кто дочитал до конца - ссылка на архив с исходным кодом модуля.

Автор: Виталий Бутурлин

Источник




К сожалению, не доступен сервер mySQL