44 Live Templates для Битрикса + ещё 49 для D7
Кому
Данная статья носит справочный характер и будет интересна лишь программистам CMS 1С-Битрикс, причем тому сегменту, который использует в разработке чудесную IDE PhpStorm. Такая комбинация встречается нередко, в нашей компании практически все программисты пользуются именно ей.
О чем
В данной IDE используется механизм расширяемых Live templates’ов. Эти template’ы (будем далее по тексту называть их LT) представляют собой заранее заданные последовательности символов, которые по нажатию на клавишу Tab можно развернуть в “заготовки” кода. Не сказать, что JetBrains первые изобрели этот механизм, но они уж точно знают, как сделать его удобными. Собственно, своими наработками для CMS 1С-Битрикс я и хотел поделиться.
Live templates 2014
Главный модуль
По старой программистской традиции, начнем с функции модуля main. Модуль растет с каждым обновлением, обилие его фич уже не упомнить - что уж говорить о названиях методов. Вот, например методы для регистрации CSS и JS ( SetAdditionalCSS и AddHeadScript соответственно). Никогда не мог понять и запомнить - почему стили надо “Установить дополнительными” а скрипты “добавить в head” (дословный перевод названий методов). Но, спасибо PhpStorm, это больше не проблема. Мои первые два LT:
addcss
$APPLICATION->SetAdditionalCSS(SITE_TEMPLATE_PATH . $END$);
addjs
$APPLICATION->AddHeadScript(SITE_TEMPLATE_PATH . $END$);
Последовательность $END$ определяет место, в котором окажется курсор после “применения” LT, то есть можно будет сразу написать путь к файлу относительно шаблона сайта.
Частенько требуется понять в шаблоне сайта, на какой же мы странице сайта (чтобы, например, не выводить новости в левом меню на основной странице новостей или скрывать хлебные крошки на главной странице). Для этого подойдут следующие два LT:
isindex
$isIndex = $APPLICATION->GetCurPage(false) == SITE_DIR;
(проверка на главную страницу)
ispage
strpos($APPLICATION->GetCurUri(), '/$END$/') !== false;
(проверка на наличие /<название_раздела>/ в адресе)
А как часто вы подключаете модули в агентах или обработчиках? По-хорошему - должны постоянно. Лидером по использованию, безусловно, является модуль Информационных блоков - поэтому для него я и создал еще один LT.
ibl
CModule::IncludeModule("iblock")
Для работы с логами отлично себя зарекомендовали такие варианты:
m2l
AddMessage2Log($END$);
m2lpr
AddMessage2Log(print_r($END$, TRUE));
Инфоблоки
Вот уж где LT нужны на каждом шаге. Вспомните только, сколько GetList’ов приходится делать! А самый известный из них, любимец публики, закладка в браузере на который есть у каждого уважающего себя битриксоида - CIBlockElement::GetList. Как же с ним бороться? А вот так:
egl
$arOrder = array("SORT" => "ASC");
$arFilter = array($END$);
$arSelectFields = array("ID","ACTIVE", "NAME");
$rsElements = CIBlockElement::GetList($arOrder, $arFilter, FALSE, FALSE, $arSelectFields);
while($arElement = $rsElements->GetNext())
{
}
Стоило перейти к инфоблокам, LT перестали быть однострочными. Но, зато сразу есть все что нужно - и массив для сортировки, и фильтр (курсор после разворачивания переместится именно туда), и поле для ограничения извлекаемых столбцов. Мне в 95% случаев хватает. Часто выручает такой вот LT, кстати, как раз для фильтра:
filter
"ACTIVE" => "Y", "SECTION_GLOBAL_ACTIVE" => "Y", "ACTIVE_DATE" => "Y"
А как же можно забыть про “старшего брата” - CIBlockSection::GetList?
sgl
$arSort = array("SORT" => "ASC");
$arFilter = array("IBLOCK_ID" => $arParams["IBLOCK_ID"], "ID" => $END$);
$rsSections = CIBlockSection::GetList($arSort, $arFilter);
while ($arSection = $rsSections->GetNext())
{
}
Как видите, я настроил LT таким образом, чтобы сразу фильтровать разделы по ID - именно такие запросы мне нужны чаще всего.
Компоненты
Я решил описать LT для компонентов после главного модуля, потому что так логичнее. Сначала мы данные выбираем, а затем уже подготавливаем и выводим. Наверняка, каждому взрослому программисту Битрикса известна конструкция для такого LT:
res
$arImg = CFile::ResizeImageGet($END$, array('width' => 150, 'height' => 85));
Это самая первая и безобидная версия LT. Магические числа, никаких проверок - все как в детстве. Сейчас она выросла до такого вот монстра.
res2
if (isset($arParams["RESIZE_TYPE"]) && $arParams["RESIZE_TYPE"] != -1 && $arParams["WIDTH"] && $arParams["HEIGHT"])
{
foreach ($arResult["ITEMS"] as &$arItem)
{
if ($arItem["PREVIEW_PICTURE"])
{
$arImg = CFile::ResizeImageGet($arItem["PREVIEW_PICTURE"], array("width" => $arParams["WIDTH"], "height" =>$arParams["HEIGHT"]), intval($arParams["RESIZE_TYPE"]), true);
if ($arImg)
{
$arItem["PREVIEW_PICTURE"]["SRC"] = $arImg["src"];
$arItem["PREVIEW_PICTURE"]["WIDTH"] = $arImg["width"];
$arItem["PREVIEW_PICTURE"]["HEIGHT"] = $arImg["height"];
}
}
}
}
А кто помнит наизусть архинеудобный метод, чтобы добавить в кеш компонента новую переменную? Черт с ним, с именем ( SetResultCacheKeys, кстати), надо же еще упомнить, для какого объекта его вызывать. А у нас используют LT:
cache
$this->__component->SetResultCacheKeys(array("$END$"));
И сразу все понятно, без мороки.
А теперь, наверное, стоит рассказать и о шаблонах компонентов. Знаете ли вы, что шаблон, например, списка новостей, можно сотворить за 1 секунду? Да еще и с поддержкой Эрмитажа и постраничкой! Это делают так:
items
<?if ($arResult["ITEMS"]): ?>
<?if($arParams["DISPLAY_TOP_PAGER"]):?>
<?=$arResult["NAV_STRING"]?>
<?endif?>
<?foreach ($arResult["ITEMS"] as $i => $arItem): ?>
<?
$this->AddEditAction($arItem['ID'], $arItem['EDIT_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_EDIT"));
$this->AddDeleteAction($arItem['ID'], $arItem['DELETE_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_DELETE"), array("CONFIRM" => GetMessage('CT_BNL_ELEMENT_DELETE_CONFIRM')));
$id = $this->GetEditAreaId($arItem['ID']);
?>
<div id="<?=$id?>">
$END$
</div>
<? endforeach?>
<?if($arParams["DISPLAY_BOTTOM_PAGER"]):?>
<?=$arResult["NAV_STRING"]?>
<?endif?>
<? endif ?>
Если шаблон уже готов и нужен только эрмитаж, то на помощь спешит getid:
getid
<?
$this->AddEditAction($arItem['ID'], $arItem['EDIT_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_EDIT"));
$this->AddDeleteAction($arItem['ID'],$arItem['DELETE_LINK'], CIBlock::GetArrayByID($arItem["IBLOCK_ID"], "ELEMENT_DELETE"), array("CONFIRM" =>GetMessage('CT_BNL_ELEMENT_DELETE_CONFIRM')));
$id = $this->GetEditAreaId($arItem['ID']);
?>
Надеюсь, мне удалось донести до консервативных от природы коллег-программистов пользу LT в PhpStorm. А теперь,в component_epilog’е, так сказать, все мои LT даром, в двух таблицах. В первой - моя таблица “аббревиатур” для популярных переменных, а “взрослые” LT во второй.
Аббревиатуры
Live template | Расшифровка |
app | $APPLICATION |
arbi | $arBasketItem |
are | $arElement |
arf | $arField |
arr | $arResult |
curr | CURRENCY |
detpic | DETAIL_PICTURE |
dett | DETAIL_TEXT |
disp | DISPLAY_PROPERTIES |
disv | DISPLAY_VALUE |
dpu | DETAIL_PAGE_URL |
prepic | PREVIEW_PICTURE |
pret | PREVIEW_TEXT |
prop | PROPERTIES |
spu | SECTION_PAGE_URL |
stp | SITE_TEMPLATE_PATH |
Основные LT
addcss | Добавить CSS файл для вывода в head’е страницы |
$APPLICATION->SetAdditionalCSS(SITE_TEMPLATE_PATH . $END$);
|
|
addjs | Добавить JS файл для вывода в head’е страницы |
$APPLICATION->SetAdditionalCSS(SITE_TEMPLATE_PATH . $END$);
|
|
ajax | Минимальный необходимый код для работы ajax-страниц (без шаблона сайта) |
<?
|
|
artp | Создать небольшой массив параметров шаблона компонента (как пример) |
$arTemplateParameters =array(
|
|
cache | Добавить ключ $arResult в кеш компонента (“пробросить” в component_epilog.php)/td> |
$this->__component->SetResultCacheKeys(array("$END$"));
|
|
dirs | Разбить URL по сегментам |
// текущий раздел строкой
|
|
egl | Получить элементы инфоблока |
$arOrder = array("SORT" => "ASC");
|
|
filter | Самый популярный фильтр для элементов инфоблоков |
"ACTIVE" => "Y", "SECTION_GLOBAL_ACTIVE" => "Y", "ACTIVE_DATE" => "Y"
|
|
foreari | Пройтись циклом по элементам в компоненте |
foreach ($arResult["ITEMS"] as $i => $arItem)
|
|
getid | Получить id элемента для Эрмитажа |
<?
|
|
getsid | Получить id раздела для Эрмитажа |
<?
|
|
ibl | Подключить модуль инфоблоков |
CModule::IncludeModule("iblock")
|
|
igl | Получить инфоблоки |
$arOrder = array("SORT" => "ASC");
|
|
inc | Вывод включаемой области |
<?$APPLICATION->IncludeFile(
|
|
isindex | Проверка на главную страницу |
$isIndex = $APPLICATION->GetCurPage(false) == SITE_DIR;
|
|
ispage | Проверка на внутреннюю страницу |
strpos($APPLICATION->GetCurUri(), '/$END$/') !== false;
|
|
items | Вывод элементов |
<?if ($arResult["ITEMS"]): ?>
|
|
log | Объявить переменную для лога |
define("LOG_FILENAME", $_SERVER["DOCUMENT_ROOT"]."/log42.txt");
|
|
m2l | Вывести выражение в лог (для примитивных типов) |
AddMessage2Log($END$);
|
|
m2lpr | Вывести выражение в лог (для массивов и объектов) |
AddMessage2Log(print_r($END$, TRUE));
|
|
pgl | Получить свойства инфоблока |
$arSort = array("SORT" => "ASC");
|
|
res | Сжать изображение |
$arImg = CFile::ResizeImageGet($END$, array('width' => 150, 'height' => 85));
|
|
res2 | Сжать анонсные изображения элементов, настройки сжатия взять из параметров |
if (isset($arParams["RESIZE_TYPE"]) && $arParams["RESIZE_TYPE"] != -1 && $arParams["WIDTH"] && $arParams["HEIGHT"])
|
|
resitmpre | Сжать анонсные изображения элементов |
foreach ($arResult["ITEMS"] as $i => $arItem)
|
|
sections | Вывести разделы в компоненте |
<?if ($arResult["SECTIONS"]): ?>
|
|
sgl | Получить разделы инфоблока |
$arSort = array("SORT" => "ASC");
|
|
ugl | Получить пользователей |
$arFilter = array("ID" => $USER->GetID());
|
Live templates 2018
В первой половине статьи описаны Live Templates для Битрикса 2014-го года. А ниже приведены шаблоны кода, которые в ходу в 2018-ом году.
Отладка
prr
echo '<pre>' . __FILE__ . ':' . __LINE__ . ':<br>' . print_r($END$, true) . '</pre>';
У каждого программиста должен быть макрос вроде этого. В этом макросе решена распространённая проблема: невозможность отыскать место вызова отладочной печати. Помимо данных выводится имя PHP файла и номер строки.
prrh
echo '<pre style=\'display: none;\'>' . __FILE__ . ':' . __LINE__ . ':<br>' . print_r($END$, true) . '</pre>';
В случае аварии отладка может применяться и на бою. Чтобы не распугать пользователей,<pre> рекомендуется выводить скрыто. А чтобы быстро отобразить все скрытые <pre> на странице можно создать в браузере такой букмарклет:
javascript:$("pre").show().css("border-color","red");void(0);
prrc
echo __FILE__ . ':' . __LINE__ . PHP_EOL . print_r($END$, true). PHP_EOL;
Для тех, кто часто запускает свой php-код через командную строку, пригодится этот LT для печати в консоль.
debug
\Bitrix\Main\Diag\Debug::writeToFile(__FILE__ . ':' . __LINE__ . "\n(" . date('Y-m-d H:i:s').")\n" . print_r($END$, TRUE) . "\n\n", '', 'log/__debug.log');
Новый метод из ядра D7 для отладки в файл. Не забудьте только создать каталог /log/ в проекте и закрыть его от просмотра через браузер с помощью .htaccess.
phpdebug
$tempFile = fopen($_SERVER['DOCUMENT_ROOT'] . '/log/__bx_log.log', 'a');
fwrite(
$tempFile,
__FILE__ . ':' . __LINE__ . PHP_EOL . '(' . date('Y-m-d H:i:s').')' . PHP_EOL
. print_r($END$, TRUE)
. PHP_EOL . PHP_EOL
);
fclose($tempFile);
Отладка в файл для php-скриптов без использования API Bitrix. Полезно, если нужно исследовать что-то до инициализации ядра или разобраться в не-Bitrix коде.
debugdb
\CEventLog::add(array(
'AUDIT_TYPE_ID' => 'DEBUG',
'ITEM_ID' => __FILE__ . ':' . __LINE__,
'DESCRIPTION' => print_r($END$, TRUE),
));
Отладочная печать прямо в журнал событий Битрикса с помощью штатного метода CEventLog::add. Журнал событий в этом плане представляет из себя довольно удобный инструмент, хотя бы потому что Битрикс периодически его очищает сам.
label
\Bitrix\Main\Diag\Debug::startTimeLabel('$NAME$');
$END$
\Bitrix\Main\Diag\Debug::endTimeLabel('$NAME$');
Чтобы узнать продолжительность выполнения блока кода достаточно окружить его парой новых методов из ядра D7.
labels
\Bitrix\Main\Diag\Debug::getTimeLabels()
Получение всех замеров по коду (см. предыдущий LT). Удобно использовать в связке с prr или debug.
die
die(__FILE__ . ":" . __LINE__)
Умереть тяжело красиво. Бывает очень неприятно отлаживать код, в котором кто-то забыл лишний die. Теперь такой проблемы не будет — если страница завершилась, то хотя бы будет видно, в каком это было файле и на какой строке.
Главный модуль
addcss
\Bitrix\Main\Page\Asset::getInstance()->addCss(SITE_TEMPLATE_PATH . $END$);
addjs
\Bitrix\Main\Page\Asset::getInstance()->addJs(SITE_TEMPLATE_PATH . $END$);
addstr
\Bitrix\Main\Page\Asset::getInstance()->addString($END$);
Трио новеньких методов из ядра D7 для добавления Css, Js и произвольных строк.
ibl2
\Bitrix\Main\Loader::includeModule('iblock')
Новый способ подключать модули через ядро D7.
isajax
\Bitrix\Main\Application::getInstance()->getContext()->getRequest()->isAjaxRequest()
Новый метод для проверки, по Ajax ли запрашивается текущая страница. Используя её, помните что злоумышленнику не составит труда обмануть сервер.
on
/**
* @see Intervolga\Custom\EventHandlers\Iblock::onAfterElementUpdate
*/
\Bitrix\Main\EventManager::getInstance()->addEventHandler('iblock', 'OnAfterIBlockElementUpdate', array('Intervolga\\Custom\\EventHandlers\\Iblock', 'onAfterElementUpdate'));
Подключение обработчика событий через D7. В примере: событие обновления элемента ИБ.
loadm
\Bitrix\Main\Localization\Loc::loadMessages(__FILE__);
Загрузка lang-переменных текущего файла через новое ядро D7.
gm
\Bitrix\Main\Localization\Loc::getMessage('$END$')
Получение lang-строки через новое ядро D7.
og
\Bitrix\Main\Config\Option::get('main', 'max_file_size')
os
\Bitrix\Main\Config\Option::set(‘main’, 'max_file_size', $END$);
od
\Bitrix\Main\Config\Option::delete('main', array(
'name' => 'max_file_size',
'site_id' => 's2',
)
);
Трио методов для работы с настройками модулей. Получение, установка, удаление. Класс \Bitrix\Main\Config\Option взамен COption.
root
\Bitrix\Main\Application::getDocumentRoot()
Получение пути до корня сайта.
send
\Bitrix\Main\Mail\Event::send(array(
"EVENT_NAME" => "NEW_USER",
"LID" => "s1",
"C_FIELDS" => array(
"EMAIL" => "info@intervolga.ru",
$END$
),
));
Создание почтового события и регистрация его для отправки.
request
\Bitrix\Main\Application::getInstance()->getContext()->getRequest()
Получение объекта запроса — альтернатива работе с $_REQUEST, $_POST, $_GET.
tarr
$this->arResult
tarp
$this->arParams
Аббревиатуры для главных полей компонентов на классах.
getlist
getList(array(
'select' => array('ID', 'CNT', $SELECT$),
'filter' => array($FILTER$),
'group' => array($GROUP$),
'order' => array('ID'),
'limit' => $LIMIT$,
'offset' => 0,
'runtime' => array(
new \Bitrix\Main\ORM\Fields\ExpressionField('CNT', 'COUNT(*)')
),
'cache' => array(
'ttl' => 3600,
'cache_joins' => true,
),
));
Полный getList к новым сущностям в ядре d7. Разумеется, из него можно что-то удалить.
Интернет-магазин
buy
\Bitrix\Catalog\Product\Basket::addProduct([
'PRODUCT_ID' => $PRODUCT_ID$,
'QUANTITY' => 1,
]);
Добавление товара в корзину. Новый добрый метод ядра D7 \Bitrix\Catalog\Product\Basket::addProduct, заменивший старый добрый Add2BasketByProductID.
Аббревиатуры для классов d7
Live template | Класс | Назначение |
application |
\Bitrix\Main\Application
|
Приложение |
opt |
\Bitrix\Main\Config\Option
|
Настройки модулей |
loc |
\Bitrix\Main\Localization\Loc
|
Локализация |
loader |
\Bitrix\Main\Loader
|
Загрузчик модулей |
diagdebug |
\Bitrix\Main\Diag\Debug
|
Отладчик |
asset |
\Bitrix\Main\Page\Asset
|
Зависимости в вёрстке |
uri |
\Bitrix\Main\Web\Uri
|
Uri |
event |
\Bitrix\Main\Mail\Event
|
Почтовое событие |
ex |
\Bitrix\Main\SystemException
|
Корневое исключение в Битриксе |
em |
\Bitrix\Main\EventManager
|
Менеджер событий |
cache |
\Bitrix\Main\Data\Cache
|
Кеш |
file |
\Bitrix\Main\FileTable
|
Таблица файлов |
group |
\Bitrix\Main\GroupTable
|
Таблица групп |
site |
\Bitrix\Main\SiteTable
|
Таблица сайтов |
element |
\Bitrix\Iblock\ElementTable
|
Таблица элментов ИБ |
iblock |
\Bitrix\Iblock\IblockTable
|
Таблица ИБ |
prop |
\Bitrix\Iblock\PropertyTable
|
Таблица свойств ИБ |
sect |
\Bitrix\Iblock\SectionTable
|
Таблица разделов ИБ |
enum |
\Bitrix\Iblock\PropertyEnumerationTable
|
Таблица вариантов списков ИБ |
store |
\Bitrix\Catalog\StoreTable
|
Таблица складов |
product |
\Bitrix\Catalog\ProductTable
|
Таблица товаров |
Live templates 2018 в виде таблицы
prr | Отладка в браузер | |
echo '<pre>' . __FILE__ . ':' . __LINE__ . ':<br>' . print_r($END$, true) . '</pre>';
|
||
prrh | Скрытая отладка в браузер | |
echo '<pre style=\'display: none;\'>' . __FILE__ . ':' . __LINE__ . ':<br>' . print_r($END$, true) . '</pre>';
|
||
prrc | Отладка в консоль | |
echo __FILE__ . ':' . __LINE__ . PHP_EOL . print_r($END$, true). PHP_EOL;
|
||
debug | Отладка в файл | |
\Bitrix\Main\Diag\Debug::writeToFile(__FILE__ . ':' . __LINE__ . "\n(" . date('Y-m-d H:i:s').")\n" . print_r($END$, TRUE) . "\n\n", '', 'log/__debug.log');
|
||
phpdebug | Отладка в файл (чистый php) | |
$tempFile = fopen($_SERVER['DOCUMENT_ROOT'] . '/log/__bx_log.log', 'a');
|
||
debugdb | Отладка в журнал событий | |
\CEventLog::add(array(
|
||
label | Замер времени выполнения | |
\Bitrix\Main\Diag\Debug::startTimeLabel('$NAME$');
|
||
labels | Получение всех замеров label | |
\Bitrix\Main\Diag\Debug::getTimeLabels()
|
||
die | Умереть красиво | |
die(__FILE__ . ":" . __LINE__)
|
||
addcss | Добавить CSS | |
\Bitrix\Main\Page\Asset::getInstance()->addCss(SITE_TEMPLATE_PATH . $END$);
|
||
addjs | Добавить JS | |
\Bitrix\Main\Page\Asset::getInstance()->addJs(SITE_TEMPLATE_PATH . $END$);
|
||
addstr | Добавить строку в <head> | |
\Bitrix\Main\Page\Asset::getInstance()->addString($END$);
|
||
ibl2 | Подключить модуль инфоблоков | |
\Bitrix\Main\Loader::includeModule('iblock')
|
||
isajax | Проверка на AJAX | |
\Bitrix\Main\Application::getInstance()->getContext()->getRequest()->isAjaxRequest()
|
||
on | Подключить обработчик события | |
/**
|
||
loadm | Загрузить lang-переменные файла | |
\Bitrix\Main\Localization\Loc::loadMessages(__FILE__);
|
||
gm | Получить lang-переменную | |
\Bitrix\Main\Localization\Loc::getMessage('$END$')
|
||
og | Получить настройку модуля | |
\Bitrix\Main\Config\Option::get('main', 'max_file_size')
|
||
os | Задать настройку модуля | |
\Bitrix\Main\Config\Option::set('main', 'max_file_size', $END$);
|
||
od | Удалить настройки модуля по фильтру | |
\Bitrix\Main\Config\Option::delete('main', array(
|
||
root | Получить путь к корню сайта | |
\Bitrix\Main\Application::getDocumentRoot()
|
||
send | Отправить почтовое событие | |
\Bitrix\Main\Mail\Event::send(array(
|
||
request | Получить объект HTTP-запроса | |
\Bitrix\Main\Application::getInstance()->getContext()->getRequest()
|
||
tarr | Поле arResult компонента на классе | |
$this->arResult
|
||
tarp | Поле arParams компонента на классе | |
$this->arParams
|
||
getlist | Найти данные с помощью D7 | |
getList(array(
|
||
buy | Добавление товара в корзину | |
\Bitrix\Catalog\Product\Basket::addProduct([
|
UPD:
Если вас заинтересовали Live Templates для Битрикса, оставьте в комментариях свой e-mail и мы поделимся файлом экспорта =)
UPD2:
Ссылка для скачивания файлика бесплатно без смс=)
UPD3:
Ссылка на Live templates (дополнения 2018-го года)
Статьи по теме
- аренда команды (от 2 человек, не менее 3 месяцев);
- итерации с фиксированной ценой (1-3 месяца длительностью).
- регулярные онлайн-планерки с заказчиком;
- квалифицированных специалистов;
- организованную команду (находятся в одном помещении, что упрощает решение рабочих вопросов);
- полную прозрачность и регулярность отчетов о результатах.
- нагруженный интернет-магазин;
- личный кабинет;
- оптовые продажи — B2B-платформа;
- маркетплейс;
- технический аудит сайта;
- Битрикс24 — корпоративные HR-порталы;
- Битрикс24 — построение CRM-системы;
- Битрикс24 — личные кабинеты сотрудников;
- Битрикс24 — аудит портала;
- 1С — интеграция с другими системами;
- 1С — доработка системы;
- маркетинг — комплексное интернет-продвижение;
- маркетинг — продвижение для B2B.