Создание плагина для бэкэнда Webasyst Shop-script 5

Цель и смысл создания плагина

Зачем вообще создавать плагины для Webasyst, Если можно просто «поковыряться» в коде приложения (того же Shop-Script 5) и изменить всё прямо там?

Я задался таким вопросом, и попытался пойти напролом без всяких плагинов. Довольно быстро я обнаружил, что при этом начинаются проблемы с автоматическими обновлениями — приходится вручную отслеживать все обновлённые инсталлером файлы, и переносить из них изменения в «актуальные версии файлов», например с помощью утилит diffuse или meld, ну или консольных diff, если совсем по хардкору. Со временем это превращается в кошмар.

UPD: Спустя некоторое время после написания этого поста я сделал движок «патчинга на лету», который позволяет хранить изменения файлов отдельно от основного кода, подверженного обновлениям, и применять их автоматически, вот этот движок: Monkey Patching для webasyst

Поэтому, наиболее простой вариант — создание плагинов, а если для них недостаточно «хуков» (это точки вызовов событий, на которые плагин можно подписать), то либо клянчить новые хуки у разработчиков либо, так уж и быть, добавить свои прямо в код.

Причина написания этой статьи

Так как справка по этой теме по состоянию на середину 2014 года весьма бедная (кстати вот она: http://www.webasyst.ru/developers/docs/plugins/ ), приходится изучать её при помощи ковыряния в готовых плагинах, и отладчика XDebug. Жаль что он не умеет «заглядывать» в Smarty-файлы шаблонов…

На форуме forum.webasyst.ru есть немного информации по теме, но чувствуется, что людям незачем делиться ей бесплатно, так как они зарабатывают разработкой плагинов за деньги. Хотя, может быть, они бы и рады поделиться, но работа отнимает всё свободное время, и просто некогда сидеть на форумах.

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

Создание основы плагина:

1. Определиться, для какого приложения создаём плагин, например это упомянутый в заголовке Shop-Script. Когда определились — находим его папку в /wa-apps/ в данном случае это /wa-apps/shop. Для упрощения, и расширения данного описания до масштабов любого приложения, для дальнейших рассуждений обзовём приложение словом [APP_ID] которое в данном случае = «shop».

2. Находим там папку plugins — туда и будем класть плагин. Если папки нет — значит у вас еще не был установлен ни один плагин, и нужно создать эту папку. Разрешения на неё поставить такие, чтобы веб-сервер в дальнейшем смог туда что-то писать — пригодится в будущем, когда захочется поставить плагин через инсталлер.

Название папки, по идее, должно соответствовать названию плагина. Например, я создаю плагин, позволяющий фильтровать товары, отображаемые в админке магазина по значениям характеристик, поэтому папка у меня называется charfilter. Это название скроем за словом [PLUGIN], равное в данном случае «charfilter»

3. Создаём минимальную структуру папок и файлов:

/wa-data/[APP_ID]/plugins/[PLUGIN]/lib — папка

/wa-data/[APP_ID]/plugins/[PLUGIN]/lib/config — папка

/wa-data/[APP_ID]/plugins/[PLUGIN]/lib/[APP_ID][PLUGIN].plugin.php — главный файл плагина

/wa-data/[APP_ID]/plugins/[PLUGIN]/lib/config/plugin.php — файл настроек плагина

4. Пишем минимальный код, для работы плагина:

В файле config/plugin.php:

<?php

return array(
// обязательные параметры
'name' => 'Фильтр товаров по характеристикам',//Название плагина
'description' => 'Позволяет фильтровать товары в админ-панели магазина по значениям характеристик',//Описание плагина
'version' => '1.0',//Версия (понадобится для автообновлений, если вы загрузите свой плагин в базу "магазин Webasyst")
// массив соответствий "событие" => "обработчик" (название метода в классе плагина),
// если плагину нужно обрабатывать какие-то события
'handlers' => array(
    'backend_products' => 'on_backend_products', //Это событие срабатывает при отображении страницы "товары" в админке
),
    // остальные параметры — необязательные, например:
'img'=>'img/plugin.png', //Это картинка, которая будет показываться в списке плагинов, соответственно папка img должна лежать в /wa-data/[APP_ID]/plugins/[PLUGIN]/
);

Где я взял название события ‘backend_products’ ?
Вот здесь: http://www.webasyst.ru/developers/docs/plugins/hooks/shop/, об этом дальше будет подробнее.

5. Создаём основной класс плагина:

В файле [APP_ID][PLUGIN].plugin.php, который в моём случае назвается shopCharfilter.plugin.php, пишем для начала такой вот код:

<?php
/**
* Класс плагина для фильтрации товаров в админ-панели
* (наследуется от ShopPlugin, в других приложениях, базовый класс будет другой, но найти его не сложно, например SitePlugin или BlogPlugin, в общем поиск по регулярному выражению "class\ +[a-Z]Plugin" в php-файлах поможет) 
*/
class shopCharfilterPlugin extends shopPlugin {
/**
*  Обработчик события backend_products
* @param array() $params параметры, передаваемые обработчику при срабатывании события
*/
public function on_backend_products($params)
{
return array();
}
}

6. Включим наш плагин, чтобы приложение его увидело:

Для этого открываем файл /wa-config/apps/[APP_ID]/plugins.php
Там есть массив array(‘название плагина’=>true, ‘название другого плагина’=>true);
Нужно добавить туда «наш плагин»=>true, т.е. в данном случае это будет ‘charfilter’=>true

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

7. Проверим, подключился ли плагин:

Для этого открываем бэк-энд магазина (в нашем случае это магазин)

открываем в браузере адрес: АДРЕС_САЙТА/webasyst/[APP_ID]/?module=plugins#/

И на левой части страницы будет список включенных плагинов, выглядящий примерно так:

Плагины

Наконец-то подготовительные работы закончены.

Что можно делать с помощью плагинов

Теперь можно попробовать разобраться с тем, что можно делать с помощью плагинов.  Каждый, кто изучал несколько языков программирования, начиная новый, пока неизвестный язык, задавался вопросом «Как здесь вывести на экран строку, и как считать введенные пользователем данные?» или каким-то похожим. Алгоритмы везде одинаковы, но вот внешний вид этих алгоритмов может быть очень разным.

Тоже самое чувство возникает, когда пытаешься задаться вопросом «как в webasyst с помощью плагинов сделать X или Y ?»

Для того, чтобы понимать, как сделать то, что вам нужно — необходимо для начала узнать что вы вообще можете делать в рамках данного инструментария.

В теории всё просто — есть «хуки» — это вызываемые в определенные моменты жизни приложения события, которые плагин будет перехватывать и что-то делать с данными, которые ему предоставлены. Но вот вопрос — что же он может сделать?

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

Справка webasyst:

Во-первых, чтобы увидеть информацию о хуках (на примере именно Shop-Script 5) можно открыть страницу http://www.webasyst.ru/developers/docs/plugins/hooks/shop/, которая содержит список всех хуков, которые умеет вызывать приложение shop-script. Открывая каждый из них, можно подивиться чрезвычайной краткости информации, и отсутствию примеров использования. Однако, кое-какая информация там всё же есть, оказывается что:

  • существуют несколько типов хуков
  • у хуков одного и того же типа, примерно один принцип действия
  • из описания и чтения исходного кода страниц вполне можно извлечь дополнительную информацию, которую там «запрятали» разработчики webasyst.
  • хуки при вызове должны что-то возвращать, если хотят оказать воздействие на результат работы сайта, если же нет — им следует возвращать null (насколько я успел понять, это универсальное правило)

Рассмотрим их подробнее:

Интерфейсные хуки — это хуки, используемые при построении интерфейса, а точнее, вызываемые перед отрисовкой страниц, или отдельных блоков.

В разделе backend_* эти хуки занимают большую часть, к ним относятся такие, как backend_customer, backend_product, backend_products, backend_set_dialog и многие другие.

Как пользоваться интерфейсными хуками:

Ваш метод, который вызывается при срабатывании интерфейсного хука, не получит в $params никаких данных, а вернуть он должен массив, содержащий кусочки кода в качестве значений, а в качестве ключей — имена областей страницы, куда надо вставлять данные кусочки.

Грубо говоря, в шаблоне smarty, которым является страница местами вставлены вот такие куски кода:

{if !empty($backend_products)}{foreach $backend_products as $_}{ifset($_.toolbar_export_li)}{/foreach}{/if}

Этот кусок кода я взял из файла Products.html, который отвечает за отображение списка товаров в админке. Однако, подобные куски легко можно найти в других файлах. Разберем что он из себя представляет:

$backend_products — это массив данных, ключи которого — это имена плагинов, а значения — это массивы, возвращённые плагинами в ответ на вызов события backend_products (одноимённого). В данном куске кода производится проход по массиву $backend_products и если в его элементах найдено поле «toolbar_export_li» то оно выводится в данном месте в текст html-страницы.

Пример кода, который делает что-то почти полезное:

Доработаем наш метод простым способом:

<?php class shopCharfilterPlugin extends shopPlugin { 
public function on_backend_products($params)
{ 	
return array('toolbar_export_li'=>'Привет!');
}
}

В результате справа на панели, где находятся варианты экспорта повявится надпись «Привет!»

Привет!

ИТОГО: как не трудно догадаться, в шаблонах разных страниц есть довольно много мест, куда можно вставить свой текст, возвращая этот текст в виде полей массива. Нужно только знать названия переменных, которые выводятся в тех или иных местах. Соответственно, визуальные хуки — это события вызываемые перед отрисовкой тех или иных страниц, или отдельных блоков страницы.

Я провёл «расследование», и нашел, какие именно переменные доступны на странице Products.html (то есть страница со списком товаров) и показал где эти переменные выводятся:

Их всего 5:

toolbar_export_li
toolbar_organize_li
toolbar_section
title_suffix
viewmode_li

И вот где они выводятся: (кликабельно)

template_vars

На этом этапе видно, как мы можем добавить новые элементы — ссылки, картинки, кнопки, выпадающие списки на страницу со списком товаров в определенные её места.

Как читать «документацию» разработчиков о хуках:

После всего вышесказанного, можно начать «врубаться» в куцые описания хуков на сайте webasyst. Для примера возьмем описание хука backend_menu и попробуем осознать, что же там имело ввиду.

http://www.webasyst.ru/developers/docs/plugins/hooks/shop/backend_menu — вот здесь описание backend_menu, говорящее о том, что входящих параметров нет, а результат работы плагина представляет собой следующее (цитата):

{$backend_menu.%plugin_id%}
{$backend_menu.%plugin_id%.aux_li}

Дополнительные ссылки рядом с элементами «Импорт/экспорт», «Настройки», «Плагины».
{$backend_menu.%plugin_id%.core_li}

Дополнительные ссылки рядом с элементами «Заказы», «Покупатели», «Товары» и т.д.
{$backend_menu.%plugin_id%.reports_li}
{$backend_menu.%plugin_id%.orders_viewmode_li}

конец цитаты.

Что сие означает?

А означает это вот то: де-то в коде (файл назвается Backend.html, найти можно поиском по файлам *.html слова $backend_menu) есть строчка, похожая на ту, что я уже приводил ранее:
{foreach $backend_menu as $_}{ifset($_.aux_li)}{/foreach}
работающая, по точно такому же принципу. То есть, вы должны вернуть из метода-обработчика массив, с ключём «aux_li» и значением произвольного html-кода, чтобы добавить в некое место страницы этот код.

Исследования файла Backend.html показывают что там есть две переменные: aux_li и core_li, в каких местах страницы они выводятся — можно увидеть на картинке:

переменные 2 (даже не стал выделять красным, и так видно куда вставился мой текст)

Думаю, что принцип работы понятен.

При этом интересно то, что место нахождения reports_li и orders_viewmode_li мне найти не удалось. Возможно это зарезервированные переменные, так как во всем приложении не нашлось ни одного файла, где они бы использовались, кроме описания события в комментариях php-кода

К сожалению, с документацией не всё так гладко

К сожалению, упомянутый выше пример является почти идеальным. В нём разработчики Shop-Script вписали в документацию названия переменных, и даже указали куда будет вставлен текст, переданный в массиве с такими названиями. Однако, далеко не везде дела обстоят так. Например, та самая страница списка товаров, про которую я рассказывал ранее, и где нашел аж 5 переменных, описанная на странице http://www.webasyst.ru/developers/docs/plugins/hooks/shop/backend_products/ не имеет вообще никакого описания! То есть обработчик backend_products судя по справке не должен делать ничего! При этом он делает. Так что, к сожалению, сообщаю, что у не удастся всюду полагаться на справку, указывающую названия ключей массива, с которыми нужно возвращать ваш собственный html-код из обработчиков интерфейсных хуков.

Работа с данными

Итак, с интерфейсными хуками разобрались, теперь, так как мне нужно, чтобы мой плагин изменял список отображаемы товаров, попробуем найти какой-нибудь хук, который вызывается перед формированием запроса к базе данных, и позволяет влиять на этот запрос. Просмотр страницы с хуками магазина даёт только один результат: хук products_collection.

Открыв описание этого хука, я увидел следующее: (цитата)

Возможность формирования коллекции товаров для отображения в бекенде или фронтенде.

Входящие параметры
$params mixed $params[‘collection’] shopProductsCollection $params[‘auto_title’] boolean $params[‘add’] boolean
 

Результат работы плагина
$return[‘null’] true, если коллекция сформирована успешно; false, если указан некорректный хеш для формирования коллекции.

Конец цитаты.

Итак, в очередной раз задаюсь вопросом: что всё это значит?

Судя по всему, в качестве входящих параметров принимается массив, содержащий элементы:

$params[‘collection’] => объект класса shopProductsCollection

$params[‘auto_title’] => boolean

$params[‘add’] => boolean

К счастью, есть зацепка — класс shopProductsCollection, который можно начать расковыривать. При этом, что означают переменные auto_title и add остаётся только гадать.

Что касается результата, который должен выдавать обработчик хука, то не совсем понятно, то ли он должен возвращать true, если ему удалось успешно сформировать коллекцию в объекте shopProductsCollection, толи нужно возвращать массив с ключём ‘null’ равный true.

В любом случае, в ходе анализа исходного кода выяснилось следующее:

1. Хук products_collection вызывается классом, лежащем в файле shopProductsCollection.class.php, в функции prepare()

2. Хук products_collection вызывается не всегда, а только тогда, когда осуществляется попытка запросить «необычный тип коллекции».
Как определяется тип коллекции вообще? Если в адресной строке есть некоторый параметр, то по нему как раз и определяется, какая коллекция должна быть открыта.

2.1 Параметр text — по видимому отображает коллекцию товаров, найденных по запросу в переменной text. Адрес может быть примерно такой: /webasyst/shop/?action=products#/products/&text=%D1%82%D0%B5%D1%81%D1%82 (здесь переменная text = слову «тест» на русском, а сформирована эта ссылка путём ввода этого слова в окошко «поиск товаров» на странице со списком оных.

2.2 Параметр tag — отображает коллекцию товаров с одним и тем-же тэгом. Адрес может выглядеть так: /webasyst/shop/?action=products#/products/tag=%D0%A1%D0%B5%D1%80%D1%8C%D0%B3%D0%B8/ — здесь слово tag равно слову «серьги», и появляется такой URL если кликнуть на этот тэг в правом нижнем углу страницы с товарами — чтобы отобразить товары, содержащие данный тэг.

2.3 Параметр category_id — соответственно товары из определённой категории

2.4 Параметр set_id — товары из определенного «набора», например «специальное предложение» или «распродажа» и т.д.

2.5 Параметр type_id — товары определенного «типа», например одежда, или бытовая техника, ну в общем это те самые типы, к которым привязываются характеристики в настройках магазина.

И что же остаётся создателям плагинов? Как отобразить товары по принципу, который не входит во все эти варианты? Для этого есть еще один параметр:

2.6 Параметр hash — это так называемых «хэш коллекции». По сути, это переменная, в которую мы можем записать новый тип коллекции, и только в том случае, если эта переменная задана, но, при этом, не задана ни одна из предыдущих — только тогда вызывается хук products_collection.

Это говорит о том, что не получится сначала отобразить список товаров из, например, тэга «серьги», а потом отфильтровать его по характеристикам — то есть сделать так, как я изначально хотел. Это как раз тот случай, когда имеет смысл попросить у разработчиков добавить новый хук, или расширить действие этого, таким образом, чтобы с помощью хука можно было повлиять на выдачу абсолютно любой коллекции товаров. Либо добавить такой хук самостоятельно, а потом, при каждом обновлении, внимательно проверять, чтобы он не был затёрт.

3. Обработчик хука должен возвращать true в том случае, если он принял значение переменной hash как своё. Но также, обработчик может вернуть массив, в котором есть поле ‘null’ равное true, видимо это сделано для «совместимости» с интерфейсными хуками, так как там всюду возвращать желательно именно массив значений (кое-где можно выводить и строку, но массив используется в подавляющем большинстве случаев). Если ни один из обработчиков (если несколько плагинов слушают событие, то и обработчиков будет несколько) не вернёт true, то приложение выкинет Exception о том, что данный hash ему непонятен — собственно об этом и указано в документации, жаль только что столь кратко.

4. Если обработчик ничего не будет делать, а просто вернёт true, то коллекция товаров будет включать в себя все товары, которые есть в магазине. То есть это равносильно тому что просто открыть страницу «товары» в админке, без каких-либо манипуляций с категориями, типами, тэгами и прочим.

5. Для того, чтобы управлять содержимым выводимой коллекции товаров, нужно работать с объектом класса shopProductCollection, который передаётся в одном из параметров, кстати, передаётся тот самый объект, который и запускает событие, то есть он передаёт туда себя самого.

Поковырявшись в классе shopProductCollection можно вынести оттуда следующую информацию:

Публичные методы, доступные для использования:

1. public function setOptions($options);

Фнкция установки «опций», где $options — массив, который может содержать следующие элементы:

array(

‘frontend’=>true/false — открывается ли страница во фронтенде? если false — в бэкэнде.

‘filters’=> если !empty, то параметры фильтрации будут взять из GET-параметров.

‘sort’ => временно ничего не делает (в коде приложения написано // !!! temporarily removed )

‘product’=> данное свойство должно содержать объект ShopProduct, и используется оно при подготовке к запросу товаров «схожих или альтернативных» (По английски это зовётся Upselling). Не уверен, что им можно пользоваться из обработчика события, так как код устроен так, что обработчик события срабатывает если не было найдено функции «подготовки запроса», а эта функция (upsellingPrepare) как раз и является таким обработчиком. Вероятно данная опция из плагина неприменима.

‘conditions’=> это должен быть массив условий, по которым определяется, какие товары являются «похожими и альтернативными» — используется только в том случае, если задано поле ‘product’ и, опять же, используется в функции upsellingProduct, поэтому вероятно, для плагинов бесполезно.

‘params’=> если !empty, то после загрузки массива товаров, к каждой из записей товаров будут подгружены параметры товаров из таблицы shop_product_params и добавлены в данный массив. Таблица эта содержит всего лишь пары name=>value, и поле product_id. По сути, эта таблица в приложении по умолчанию нигде не используется, а значит предназначена разработчиками как раз для всяких плагинов. Так что, если вам нужно добавить к товару произвольные поля — можно добавлять их в  shop_product_params а потом загружать вместе с товаром, передавая в setOptions(array(‘params’=>true))

‘absolute’=> false/string использовать ли абсолютные пути к картинкам приложенным к товарам? Параметр передаётся в shopImage::getUrl при генерации адреса картинки происходящего при обработке списка загруженных товаров до его отдачи вызывающей функции. Не знаю, насколько это свойство полезно.

);

2. public function filters($data);
Функция применения фильтров, где $data — это массив фильтров.

То есть массив ключ=>значение, где каждый ключ — это поле, по которому идёт фильтрация, а значение — собственно отбираемое при фильтрации значение этого поля. Правда, на самом деле не всё так просто 🙂

Среди полей можно задавать несколько «стандартных»:

in_stock_only‘ = true/false — если установлено в true то будут отобраны только те товары, которых на складе >0 или бесконечность (бесконечность соответствует значению NULL в поле count),

‘price_min’, ‘price_max’ — числовые значения для фильтрации товаров по цене, тут думаю всё понятно.

Остальные поля трактуются следующим образом:

В качестве ключей должны быть коды-названия характеристик товаров. Например, если у вас есть характеристика «Размер», то скорее всего, её код = «razmer». Этот код хранится в поле code таблицы shop_features а в настройках характеристик товаров его можно изменить — он находится под русским названием характеристики в виде серой мелкой надписи и карандашика.

Вот примерно как здесь:

Редактор характеристики и поле "code"

Так вот, продолжаем. Значением поля массива должно быть 1 из 3-х вариантов:

1) одно значение, равное значение характеристики, по которой мы хотим фильтровать

2) массив значений, по которым мы хотим фильтровать

3) массив содержащий поля ‘min’, ‘max’ и ‘unit’ (или хотя-бы что-то одно из этого) — позволяет фильтровать диапазон значений, да еще и с учётом единицы измерения.

Итак, пример массива, который можно передать в функцию filters:

array(‘in_stock_only’=>true, ‘price_min’=>100, ‘price_max’=>1000, ‘razmer’=>array(‘min’=>10,’max’=>50), ‘znak_zodiaka’=>947);

таким образом будут отфильтрованы товары, имеющие цену от 100 до 1000, находящиеся на складе, имеющие размер от 10 до 50, и подходящие к знаку зодиака «Овен» (он у меня как раз имеет номер 947 в таблице shop_feature_values_varchar

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

3. public function orderBy($field, $order = ‘ASC’); 

Функция установки полей для сортировки генерируемого списка товаров.
Где: $field — это либо 1 название поля, по которому нужно сортировать, либо массив из 2-х элементов, где $field[0] — название поля, а $field[1] — порядок сортировки (‘ASC’ или ‘DESC’).
$order — порядок сортировки (‘ASC’ или ‘DESC’), если первый параметр задан массивом из 2-х элементов, то этот параметр будет перезаписан вторым из элементов.

Поле $field может содержать имя любого поля товаров, в том числе специальное значение «rand()» которое обеспечивает сортировку в случайном порядке.

В качестве результата функция возвращает то, что она установила в качестве ORDER BY (в виде строки)

4. public function getOrderBy(); Функция возвращает последний столбец и его порядок сортировки из тех, что записаны в защищенной переменной класса. Не очень полезная функция для плагина.

5. public function getSQL(); Функция возвращает SQL-запрос, который будет применён для поиска товаров. Польза от данной функции в контексте плагинов — исключительно отладочная

6. public function count(); Функция подсчитывает количесто товаров, которые будут загружены и возвращает его.

7. public function getProducts($fields = «*», $offset = 0, $limit = null, $escape = true); Функция получает собственно набор товаров.

Параметры:
$fields — набор полей через запятую, которые нужно выбрать из таблицы
$offset — отступ от начала списка (аналог Mysql OFFSET)
$limit — хитрая переменная. Если = true или false, то её значение записывается в переменную $escape. Скорее всего это было сделано потому, что раньше этой переменной не было, а вместо неё СРАЗУ стояла $escape, и для совместимости прикрутили такой костыль. Если же переменная — числовая, то она равна количеству загружаемых товаров.
$escape — определяет, надо ли натравливать функцию htmlspeacialchart на те поля, где могли притаиться нехорошие значки, наподобии «>», «<» а также разные кавычки, которые могут внести раздор в красивую вёрстку страницы, а то и вообще встроить в неё вражеский JavaScript.

В качестве результата возвращается массив товаров, каждый из которых — это массив полей и их значений.

8. public function getTitle(); Функция получает заголовок страницы, соответствующий данной коллекции.

9. public function addTitle($title, $delim = ‘, ‘); Добавить к сущестующему заголовку еще «кусочек».

$title — кусочек текста, который надо добавить к заголовку.

$delim — разделитель, который будет использован, если заголовок там уже есть. Если там было пусто, то без всяких разделителей сразу будет вставлен $title

10. public function groupBy($clause); Функция жестоко и безжалостно переписывает GROUP BY параметр запроса

11. public function addWhere($condition); Функция добавляет дополнительное WHERE-условие к уже существующим.

12. public function addJoin($table, $on = null, $where = null); Функция добавляет JOIN к уже существующим.

$table — таблица, которую нужно приджойнить

$on — условие JOIN’a

$where — дополнительное условие $where

Функция возвращает алиас, под которым отныне будет известна приджойненная таблица.

13. public function getFeatureValueIds(); Функция возвращает ВСЕ ID значений характеристик, которые есть у товаров в данной выборке. Возвращаются только уникальные ID. Формат возвращаемых данных array(‘123’=>array(4,5,6),’124’=>array(8,9,10)) — это означает, что у выбранных товаров есть характеристики №123, 124, и соответствующие значения этих характеристик. Это может помочь чтобы создать гибкий фильтр по характеристикам который будет скрывать те из них, которые недоступны в данном подмножестве товаров.

14. public function getPriceRange(); Функция возвращает массив array(‘min’=>123,’max’=>321) где передаются минимальная и максимальная цена в данной выборке товаров

Наконец-то функции закончились!

Реализация полезного функционала плагина.

Перейду непосредственно к реализации плагина, ради которого всё это и затевалось.

Для начала — проектирование.

Мой плагин должен давать возможность фильтрации по характеристикам товаров в бэкэнде.

1) Он должен добавлять свой интерфейс выбора фильтров в страницу бэк-энда.

На мой взгляд, наиболее подходящее место из доступных для модификации — это  область называющаяся «viewmode_li», задавать которую буду с помощью обработчика события backend_products

2) Также, поскольку места мало, лучше сделать настройки фильтров в виде некоего выпадающего блока

3) Для фильтрации будем использовать упомянутый выше хук products_collection

4) Также, поскольку в магазине есть очень много характеристик, то нужно дать пользователю возможность выбора типа товаров, чтобы отображать характеристики только для данного типа.

Дополнительные файлы

Создаю в папке /wa-apps/[APP_ID]/plugins/[PLUGIN]/ дополнительные папки:

templates — для шаблонов Smarty. Можно было конечно весь html-код писать в самом php-файле, но это «моветон».

js — для скриптов

img — она там уже была создана для иконки плагина, сюда положу дополнительные картинки

Обработчик on_products_collection

Данный обработчик должен делать следующее:

1) Получать список существующих типов товаров, желательно сразу с количествами данных товаров по типам

2) Получать список всех характеристик и их значений

3) Передавать всё это добро в шаблон

4) Шаблон должен всё это отображать в удобоваримом виде

5) Функционал выборки типов характеристик пусть работает на Javascript.

Код обработчика:

 

 

… Пост не дописан, продолжение следует …


16 комментариев

  • Ответить Андрей |

    Спасибо, познательно в некоторых моментах )

    > И на левой части страницы будет список включенных плагинов, выглядящий примерно так:

    А вот здесь — не совсем так. После добавления — никакой информации о плагине…

    • Ответить mihanentalpo |

      Спасибо за чтение 🙂
      Если нет информации о плагине — вероятно он и не включился. Нужно попробовать с помощью него вывести где-нибудь какую-нибудь строку с помощью интерфейсного хука. Если выведет — значит он работает и нужно копать дальше. Если нет — значит он так и не был включён, и именно поэтому он и не отображается на панели слева

    • Ответить mihanentalpo |

      Дополнение: сам недавно делал новый плагин и забыл добавить его в /wa-config/apps/shop/plugins.php. Разумеется он не появился на левой панели страницы в описываемом месте.

    • Ответить mihanentalpo |

      Статья пока ждет завершения написания самого плагина, который задерживается так как срочно понадобилось сделать другой плагин, выполняемый по cron-у. Про него тоже напишу позже, а эту статью закончу как только смогу.

  • Ответить Андрей Савкин |

    Спасибо за статью, надеюсь продолжение будет =) Сам недавно начал изучать создание плагинов под shop-script 5, сейчас пытаюсь создать простой плагин для копирования товара в бекэнде и никак не получается связать кнопку выведенную через хук с экшеном в плагине, буду благодарен если поможете)

    • Ответить mihanentalpo |

      Вот здесь http://www.webasyst.ru/au/developers/docs/plugins/plugin-basics написано, что для вызова плагина в бэкэнде надо запросить URL:
      /webasyst/[APP_ID]/?plugin=[PLUGIN]&action=[ACTION]
      тогда будет произведено обращение к экшону, лежащему в файле:
      wa-apps/[APP_ID]/plugins/[PLUGIN]/lib/actions/[app_id][PLUGIN]PluginBackend[ACTION].action.php,
      класс которого должен называться:
      [app_id][PLUGIN]PluginBackend[ACTION]Action
      Например, если приложение = shop, плагин называется «MegaEditor» а экшон «ShowProduct» то:
      URL = /webasyst/shop/?plugin=MegaEditor&action=ShowProduct
      Файл = wa-apps/shop/plugins/MegaEditor/lib/actions/shopMegaEditorPluginBackendShowProduct.action.php
      Класс = shopMegaEditorPluginBackendShowProductAction
      Удачной борьбы с этими огромными названиями 🙂
      Вот так всё запутано и замудрено, вместо того чтобы использовать namespace’ы, например. Впору писать скрипт-генератор файлов, которому указываешь 3 параметра APP_ID, PLUGIN_ID и ACTION а он тебе создаёт в нужном месте файл из шаблона и даёт ссылку для обращения к этому экшону.

  • Ответить Сергей |

    Мы являемся оптовой компанией, у нас в 1С есть 3 типа цен, крупный опт, мелкий опт и розница, но выгружать на сайт можно только 1 любой тип цен, а все 3 сразу невозможно.
    Так же сформировать оптовую цену на сайте невозможно, так как в 1С цена формируется от закупочной с добавлением наценки, а на сайте сделать наценку например от крупного опта невозможно, можно только сделать скидку. Получается несоответствие цены на сайте, цене в 1С. Кроме того оптовый покупатель заходя через личный кабинет видит розничную цену и скидку от неё, а должен видеть только оптовую.
    Высылаю Вам основные требования к плагину:
    1) Выгрузка товара на сайт с несколькими типами цен, минимум 3.
    2) Возможность на некоторые товары выгружать несколько типов цен, а на некоторые только 1 тип цен (розницу)
    Что бы было возможно выбрать товары на которые установить только 1 тип цен(розницу). Такие товары должны отображаться на сайте без авторизации и при входе в личный кабинет у пользователей, у которых установлен статус «Магазин» (розница). Для пользователей, у которых установлен статус «крупный опт» или «мелкий опт», при входе в личный кабинет, данный товар отображаться не должен.
    3) Возможность выгрузки Названия, описания, артикул, страну производителя, цены, остатки и все фото из 1С.
    4) Автоматическое обновление.
    5) Двусторонняя синхронизация сайта и 1С. Возможность как выгружать товар из 1С на сайт , так и из сайта в 1С.
    6) Возможность выгружать товары с привязанными к ним «рекомендованными товарами». В 1С должна появиться опция «рекомендованные товары», что бы к каждой позиции в номенклатуре можно было привязать не менее 3-х рекомендованных товаров.
    7) Каждой категории пользователей, должен соответствовать свой тип цен.
    а) цена(розница) отображалась при входе на сайт без регистрации и у зарегистрированных пользователей в при входе в личный кабинет, если у них статус «магазин» (розница)
    б) цена крупный опт отображалась только в при входе в личный кабинет у пользователей, которым присвоен статус «крупный опт»
    в) цена мелкий опт отображалась только в при входе в личный кабинет у пользователей, которым присвоен статус «опт»
    Необходимо сделать, что бы каждый пользователь в личном кабинете видел ТОЛЬКО свой тип цен. Что бы оптовая цена отображалась не только в каталоге, но и в корзине и заказы от пользователей с оптовым статусом тоже формировались по оптовым ценам
    8) Возможность автоматически сформировать счет на оплату прямо на сайте.
    9) Заказы должны автоматически поступать из сайта в 1С.
    10) Нумерация заказов покупателя и счетов на оплату(по безналу) должна вестись по отдельной номерной линейке. Нумерация не должна задваиваться или нарушаться, если часть заказов будет поступать не с сайта, а формироваться менеджером напрямую в 1С.
    11) Отправка сообщения об изменении статуса заказа из 1С на сайт. Отправка почтового уведомления клиенту.

    Есть ли у Вас плагин отвечающий всем перечисленным требованиям? Сколько он будет стоить? Просьба выслать ответ на указанную почту etnogalerey@mail.ru.

    • Ответить mihanentalpo |

      Здравствуйте. Ответил вам на почту. На всякий случай продублирую здесь.
      Такого плагина у меня нет, и, насколько я знаю устройство ШопСкрипта, сделать его — задача очень нетривиальная.
      Хотя один мой знакомый начинал делать такой плагин, и возможно, что-то уже получилось, я выясню.
      Здесь ваш огромный комментарий оставлю чтобы кто-то другой, кто зайдёт сюда, смог прочитать его, и возможно предложить вам свою помощь.

  • Ответить Алекс |

    Добрый день!
    У меня есть интернет-магазин на shop script. устанавливал плагины себе, но есть такие вещи котрых нет пока в плагинах и я хотел бы попробывать сделать самому. но вот не задача: я не знаю ничего в программировании и во всем этом. но есть желание. подскажите с чего начать. т.к. сказать азбука, чтобы не ходить по дебрям. а начать с этого и в конце концов прийти к написанию плагина.

    • Ответить mihanentalpo |

      Здравствуйте.
      Если вы ничего не знаете в программировании, могу порекомендовать следующее:
      1) Начните изучать PHP по какому-нибудь самоучителю, их в интернете полно, уроки, видео-уроки и куча книг.
      В частности вам понадобится знать:
      1.1) Все основы PHP (написание простых скриптов в процедурном стиле)
      1.2) Объектно-ориентированное программирование
      1.3) Работа с базой данных (придётся изучить язык SQL)
      1.4) Различные структуры данных, работа с ними (это относится к программированию в целом)
      2) Параллельно будете изучать HTML, CSS и JavaScript
      3) После этого, вам желательно попробовать написать с нуля парочку собственных сайтов — чтобы в живую столкнуться с различными проблемами, научиться отлаживать скрипты, и пользоваться разным функционалом PHP, HTML, JS, CSS
      4) Вот только после всего этого вы будете по-настоящему готовы к тому, что всерьез начинать делать плагины для шопскрипта, так как это зачастую требует весьма недюжинного опыта и изобретательности по преодолению искусственно созданных препятствий, а также просто по анализу кода (ведь документации часто бывает недостаточно).

      В целом это выглядит страшновато, но тут главный вопрос — какова ваша цель. Если цель — научиться программировать — тогда вперёд, дорогу осилит идущий. Если же цель просто научиться делать плагины для ШопСкрипт чтобы пользоваться ими — лучше уходить с Шоп-скрипта на какой-нибудь другой магазин, потому что написание плагинов здесь без всех вышеописанных знаний — очень сложная задача. Даже со всеми этими знаниями и несколькими годами веб-разработки, для меня плагины для ШопСкрипта всё ещё являются местами трудной задачей. Я не могу точно утверждать, но вероятно другие, более долгоживущие и более зрелые движки имеют структуру и логику, которую новичку с минимумом знаний будет легче понять. Для этого нужно пошариться по интернету.

      Удачи, и извините что мой ответ вас не порадовал 🙂

    • Ответить mihanentalpo |

      К сожалению продолжения нет и не предвидится, так как давно уже забросил ShopScript. Слишком муторный движок. Хотя я в целом движки для магазинов и сами магазины не очень люблю.

    • Ответить mihanentalpo |

      Сейчас просмотрел ещё раз статью и вспомнил что я и сам этот блог завёл из ненависти к ShopScript’у с которым пришлось потратить кучу времени и нервов на то чтобы хоть что-то понять (и то, в результате я сделал ряд «костылей» и специальный модуль патчинга налету: https://mihanentalpo.me/2015/09/модификация-классов-webasyst-и-в-частности-shop-script/)

Оставить комментарий