В коде происходит выборка 30 элементов, первыми из которых пойдут элементы у которых есть совпадения тегов "Personal" и "2020", далее хотя бы одного из этих двух Тегов, и в конце статьи, не имеющие совпадений.
По желанию можно добавить условие фильтра для выборки только тех элементов, которые содержат хоть один Тег. Например, условие по одному Тегу будет выглядеть так:
array(
"LOGIC" => "OR",
array(
'%TAGS' => " ".$sTagName."," // строка целиком в середине
),
array(
'TAGS' => $sTagName.",%" // строка целиком в начале
),
array(
'TAGS' => "% ".$sTagName // строка целиком в конце
),
array(
'TAGS' => $sTagName // единственная строка в тегах
)
)
Пример запроса SQL:
select ((case when LOCATE('Personal', TAGS)>0 then 1 else 0 end) + (case when LOCATE('2020', TAGS)>0 then 1 else 0 end)) as SORT,
TAGS, ID fr om b_iblock_element WHERE IBLOCK_ID=18 ORDER BY SORT DESC LIMIT 0, 30;
2) Подключение на уровне ядра с помощью Loader::registerAutoLoadClasses(...) (CModule::AddAutoloadClasses(...)) Пример:
CModule::AddAutoloadClasses(
'', // не указываем имя модуля
array(
// ключ - имя класса, значение - путь относительно корня сайта к файлу с классом
'CMyClassName1' => '/path/cmyclassname1file.php',
'CMyClassName2' => '/path/cmyclassname2file.php',
)
);
Как я понимаю, разница в том, что spl_autoload_register подключает класс динамически, на основе имени, а Loader::registerAutoLoadClasses требует прописать явно класс и путь до файла.
Хотелось бы выяснить какой способ предпочтителен, их плюсы и минусы. Жду Ваших комментарий, коллеги. Возможно вы предложите альтернативные варианты.
P.S. Замечал, что при spl_autoload_register как-то не сразу новый класс подключает (может, конечно, я что-то не так делал), но метод класса зачастую начинают работать спустя какое-то время...
SR Trionikl, в битриксе нет composer и быть не может, это ведь сторонняя утилита. В битриксе есть только конфиг для composer, который там только для виду, по сути.
Хочу поделиться способом как вывести информацию по доставке в карточке товара на основе служб доставки. Имеем несколько Служб доставки по группам местоположений: по Москве, по Санкт-Петербургу, и некоторых других регионов.
Настройки у всех похожи: Ограничения по конкретным местоположениям и ограничения по стоимости. Пример показан на рисунке:
Нам нужно в Карточке Товара вывести информацию по стоимости доставки в регион и описание из службы доставки. Основная трудность заключается в том, чтобы отфильтровать Службы доставки по регионам. Ограничения по регионам хранятся в таблице b_sale_delivery2location. Нам нужны входящие местоположения, т.е. с LOCATION_TYPE = "L". Был написан следующий метод, решающий нашу задачу:
На вход подаем $sLocationCode - Код местоположения из наших местоположений 2.0; $fPrice - стоимость товара; $obEntityLocation2Delivery - объект, с описанием таблицы b_sale_delivery2location Объект $obEntityLocation2Delivery мы описываем в методе getLocationToDeliveryEntity():
Таким образом в карточке товара выводится стоимость доставки в регионе с учетом самой стоимости товара:
Срок доставки рассчитывается отдельно в зависимости от наличия и сроков производства. Если товар есть в наличии на складе, обслуживающим данный регион, то срок доставки в течении 5 дней, если нет - то берется срок производства + 5 дней на доставку. Города, которые обслуживает склад хранятся в пользовательском свойстве у склада UF_SERVICE_LOCATION (хранит код местоположения), срок производства зависит от типа товара и рассчитывается отдельно. Основная "фишка" метода: дополнительное ограничение выборки по конкретному товару из Таблицы наличия \Bitrix\Catalog\StoreProductTable. Получился такой метод:
**
* Получаем срок доставки товара в днях. Срок зависит от наличия: если на складе, обслуживающим этот город, нет в
* наличии, то рассчитывается исходя из сроков производства + сроков поставки
*
* @param string $sLocationCode - код местоположения 2.0
* @param int $iSkuId - ID Торгового предложения (товара)
* @param array $arHomeDelivery - Массив с информацией по доставке, который нужно дополнить сроками
* @return array
*/
private function getHomeDeliveryPeriod(string $sLocationCode, int $iSkuId, array $arHomeDelivery): array
{
try {
$obStore = \Bitrix\Catalog\StoreTable::getList(array(
"filter" => array(
"=UF_SERVICE_LOCATION" => $sLocationCode,
),
"select" => array(
"PRODUCT_STORE.AMOUNT",
"ID",
),
"limit" => 1,
"runtime" => array(
new ReferenceField(
"PRODUCT_STORE",
"\Bitrix\Catalog\StoreProductTable",
array(
"=this.ID" => "ref.STORE_ID",
"ref.PRODUCT_ID" => new \Bitrix\Main\DB\SqlEx * pression("?i", $iSkuId)
),
array(
"join_type" => "LEFT"
)
),
),
"cache" => array(
"ttl" => self::CACHE_TIME,
)
));
if ($arStore = $obStore->fetch()) {
if (intval($arStore["CATALOG_STORE_PRODUCT_STORE_AMOUNT"]) <= 0) {
if (intval($arStorePeriodByStore[$arProductType["UF_XML_ID"]]) > 0) {
$arHomeDelivery["DELIVERY_DAYS"] = $this->iManufacturePeriod +
self::HOME_DELIVERY_PERIOD;
}
} else {
$arHomeDelivery["DELIVERY_DAYS"] = self::HOME_DELIVERY_PERIOD;
}
}
} catch (\Bitrix\Main\ObjectPropertyException | \Bitrix\Main\ArgumentException | \Bitrix\Main\SystemException $obException) {
AddMessage2Log($obException->getMessage(),"main");
}
return $arHomeDelivery;
}
Как видно из кода, фильтрация по товару и складу происходит за счет условий:
"=this.ID" => "ref.STORE_ID",
ref.PRODUCT_ID" => new \Bitrix\Main\DB\SqlEx * pression("?i", $iSkuId)
Колдовство да и только) А есть вообще какая нибудь документация, в которой будет описано, что эти данные можно получить вот таким простым запросом, не как ваш, или как я писал сам джойня таблицы, а что-то такое, что позволяет принять цену товара и регион получения(а там еще и вес, и габариты, и какой нибудь параметр инфоблока), а на выдачу дать службы доставки, которые подходят для этого. В общем что-то от разработчиков, чтобы не городить подобное. Все же этот запрос я нахожу достаточно стандартным, чтобы вывести его в апи, или по крайней мере, описать методы, которыми можно вызвать проверку ограничений в отрыве от существования заказа/объекта заказа.
Жигулин Дмитрий, есть механизм проверки доступности Службы доставки, но не уверен что его можно найти в документации, однако именно так проверяется в ядре. Суть в том, что сначала получаем список активных служб доставки через
Проверяем каждую службу доставки на ограничения по весу товара, по местоположению, по сумме. Если какая-то проверка не проходит, то убираем службу доставки из общего списка.
Хочу поделиться способом автоматической минификации стилей и скриптов. Решение было сделано на БУС, но подойдет для других CMS. Описанное решение может оказаться полезным тем, у кого стили распределены по компонентам и не настроены автоматические сборщики типа gulp или webpack и д.р.
Думаю все знают про настройки Оптимизации CSS и JS в главном модуле, а именно "Подключать минифицированные версии CSS и JS файлов". К сожалению в Битриксе не заложена функция создания минифицированных файлов (*.min.css и *.min.js), но зато заложена опция подключения таких файлов.
CSS
Давайте начнем с CSS. Решение базируется на модуле uglifycss Node.js (https://www.npmjs.com/package/uglifycss) . Устанавливаете uglifycss для использования в командной строке (For a command line usage), это очень важно! Заранее позаботьтесь о необходимых доступах к серверу. Выполните к консоле команду:
$ npm install uglifycss -g
После успешной установки узнайте путь нахождения установленного модуля, выполнив команду. К примеру у меня, на боевом сервере был свой путь, а на серверах разработки другой.
whereis uglifycss
В ответе будет строка с путём до uglifycss. В моём случае - /usr/local/bin/uglifycss. Далее мы этот путь будем использовать в классе.
Сам класс:
<?
/*
* Класс по минификации CSS-файлов. Использует NodeJS плагин uglifycss
*/
class CCssMinify
{
const DIRS_FOR_CSS_MINIFY = [
'/local/admin/css/',
'/local/components/',
'/local/templates/main/components/',
'/local/templates/main/theme/build/styles/'
];
const FILES_FOR_CSS_MINIFY = [
'/local/templates/main/styles.css',
'/local/templates/main/template_styles.css'
];
private static $bNeedCacheRefresh = false;
/**
* Агент по минификации стилей
*
* @return string
*/
public static function minifyAgent(): string
{
try {
self::minify();
} catch (Exception $obException) {
CEventLog::Add(array(
"SEVERITY" => "ERROR",
"AUDIT_TYPE_ID" => "MINIFY_CSS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => $obException->getMessage()
));
}
return __METHOD__.'();';
}
/**
* Минификация стилей, находящихся внутри директорий, обозначенных в константе \dh\CCssMinify::DIRS_FOR_CSS_MINIFY,
* и в файлах в константе \dh\Seo\CCssMinify::FILES_FOR_CSS_MINIFY
*/
public static function minify()
{
foreach (self::DIRS_FOR_CSS_MINIFY as $sDir) {
$arFilePaths = self::findCssFilesInDir($sDir);
if (!empty($arFilePaths)) {
foreach ($arFilePaths as $sFilePath) {
self::execUglifycss($sFilePath);
}
}
}
foreach (self::FILES_FOR_CSS_MINIFY as $sFilePath) {
self::execUglifycss($sFilePath, true);
}
if (self::$bNeedCacheRefresh && BXClearCache(true, "/css/")) {
CEventLog::Add(array(
"SEVERITY" => "INFO",
"AUDIT_TYPE_ID" => "MINIFY_CSS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => "Clear cache"
));
}
}
/**
* Получим путь до UglifyCss
* @return string
*/
private static function getUglifyCssPath(): string
{
return '/usr/local/bin/uglifycss';
}
/**
* Поиск Не минифицированных css файлов в Директории (и её поддиректориях)
*
* @param $sDirPath
* @return array
*/
public static function findCssFilesInDir($sDirPath): array
{
$arFilePaths = [];
$obDirectory = new RecursiveDirectoryIterator($_SERVER["DOCUMENT_ROOT"].$sDirPath);
$obIterator = new RecursiveIteratorIterator($obDirectory);
foreach ($obIterator as $obInfo) {
$file_formal = substr($obInfo->getfileName(), strrpos($obInfo->getfileName(), ".") + 1);
$name_search = array("css"); // Список форматов
foreach ($name_search as $key_name) {
if (
$file_formal == $key_name &&
!stristr($obInfo->getfileName(), '.min.css')
) {
$arFilePaths[] = $obInfo->getPathname();
}
}
}
return $arFilePaths;
}
/**
* Выполнить минификацию
*
* @param $sFilePath
* @param bool $bAddDocumentRoot
*/
private static function execUglifycss($sFilePath, bool $bAddDocumentRoot = false)
{
if ($bAddDocumentRoot) {
$sFilePath = $_SERVER["DOCUMENT_ROOT"].$sFilePath;
}
$sMinFilePath = str_replace('.css', '.min.css', $sFilePath);
if (
!file_exists($sMinFilePath) ||
(
file_exists($sMinFilePath) &&
filectime($sFilePath) > filectime($sMinFilePath)
)
) {
/*
* https://www.npmjs.com/package/uglifycss
--output f puts the result in f file
*/
shell_exec(self::getUglifyCssPath().' '.$sFilePath.' --output '.$sMinFilePath);
self::$bNeedCacheRefresh = true;
}
}
}
В приведенном выше классе есть метод \CCssMinify::getUglifyCssPath, в котором как раз мы возвращаем путь до uglifycss, полученный через whereis uglifycss. Мы не используем специфических настроек запуска uglifycss, только путь до минифицированной версии, которая будет находится рядом с основной.
Сам Агент реализован методом \CCssMinify::minifyAgent(); Как видно из кода, метод автоматически очищает CSS КЕШ сайта. Пересозданию минификаций подвержены только те файлы css, время изменения которых позже созданных для них минификаций. В Административном разделе агент имеет следующие настройки:
Среднее время выполнения агента на моём проекте: 7-8 секунд, для ПОЛНОГО пересоздания. В режиме обычно работы, при пересоздании только изменённых файлов, не превышает 0.1 секунды. Не берусь говорить точно об объёмах, но проект довольно крупный, так что не сильно беспокойтесь, что всё повиснет. Хотя перестраховаться не мешает.
Пример результата работы агента:
C CSS мы закончили. давай перейдем к JS.
JS
Решение базируется на модуле uglifyjs Node.js (https://www.npmjs.com/package/uglify-js). Устанавливаете uglifyjs для использования в командной строке (From NPM for use as a command line app), это очень важно! Заранее позаботьтесь о необходимых доступах к серверу. Выполните к консоле команду:
npm install uglify-js -g
После успешной установки узнайте путь нахождения установленного модуля, выполнив команду.
whereis uglifyjs
В ответе будет строка с путём до uglifyjs. В моём случае - /usr/local/bin/uglifyjs. Далее мы этот путь будем использовать в классе.
Сам класс:
<?
/*
* Класс по минификации JS-файлов. Использует NodeJS плагин uglifyjs
*/
class CJsMinify
{
const DIRS_FOR_JS_MINIFY = [
'/local/admin/js/',
'/local/templates/main/js/',
'/local/components/',
'/local/templates/main/components/'
];
/**
* Агент по минификации скриптов
*
* @return string
*/
public static function minifyAgent()
{
try {
self::minify();
} catch (Exception $obException) {
CEventLog::Add(array(
"SEVERITY" => "ERROR",
"AUDIT_TYPE_ID" => "MINIFY_JS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => $obException->getMessage()
));
}
return __METHOD__.'();';
}
/**
* Минификация скриптов, находящихся внутри директорий, обозначенных в константе \dh\CJsMinify::DIRS_FOR_JS_MINIFY
*/
public static function minify()
{
$bNeedCacheRefresh = false;
foreach (self::DIRS_FOR_JS_MINIFY as $sDir) {
$arFilePaths = self::findJsFilesInDir($sDir);
if (!empty($arFilePaths)) {
foreach ($arFilePaths as $sFilePath) {
$sMinFilePath = str_replace('.js', '.min.js', $sFilePath);
if (
!file_exists($sMinFilePath) ||
(
file_exists($sMinFilePath) &&
filectime($sFilePath) > filectime($sMinFilePath)
)
) {
/*
* https://www.npmjs.com/package/uglify-js
-c, --compress [options] Enable compressor/specify compressor options
-m, --mangle [options] Mangle names/specify mangler options
-o, --output <file> Output file path (default STDOUT)
*/
shell_exec(self::getUglifyJsPath().' '.$sFilePath.' -c -m -o '.$sMinFilePath);
$bNeedCacheRefresh = true;
}
}
}
}
if ($bNeedCacheRefresh && BXClearCache(true, "/js/")) {
CEventLog::Add(array(
"SEVERITY" => "INFO",
"AUDIT_TYPE_ID" => "MINIFY_JS",
"MODULE_ID" => "main",
"ITEM_ID" => "",
"DESCRIPTION" => "Clear cache"
));
}
}
/**
* Получим путь до UglifyJS
* @return string
*/
private static function getUglifyJsPath() {
return '/usr/local/bin/uglifyjs';
}
/**
* Поиск Не минифицированных js файлов в Директории (и её поддиректориях)
*
* @param $sDirPath
* @return array
*/
public static function findJsFilesInDir($sDirPath)
{
$arFilePaths = [];
$obDirectory = new RecursiveDirectoryIterator($_SERVER["DOCUMENT_ROOT"].$sDirPath);
$obIterator = new RecursiveIteratorIterator($obDirectory);
foreach ($obIterator as $obInfo) {
$file_formal = substr($obInfo->getfileName(), strrpos($obInfo->getfileName(), ".") + 1);
$name_search = array("js"); // Список форматов
foreach ($name_search as $key_name) {
if (
$file_formal == $key_name &&
!stristr($obInfo->getfileName(), '.min.js') &&
!stristr($obInfo->getfileName(), '.map.js')
) {
$arFilePaths[] = $obInfo->getPathname();
}
}
}
return $arFilePaths;
}
}
В приведенном выше классе есть метод CJsMinify::getUglifyJsPath, в котором как раз мы возвращаем путь до uglifyjs, полученный через whereis uglifyjs. В коде имеются пояснения выставленных опций, с которыми подробнее можете ознакомится на странице https://www.npmjs.com/package/uglify-js
Сам Агент реализован методом CJsMinify::minifyAgent(). Как видно из кода, метод автоматически очищает JS КЕШ сайта. Пересозданию минификаций подвержены только те файлы js, время изменения которых позже созданных для них минификаций. В Административном разделе агент имеет следующие настройки:
Среднее время выполнения агента на моём проекте: 30-32 секундs, для ПОЛНОГО пересоздания. В режиме обычной работы, при пересоздании только изменённых файлов, не превышает 0.1 секунды.
Пример результата работы:
Бонус:
В качестве бонуса поделюсь примером .gitignore, чтобы не засорять репозиторий минификациями:
Добрый день! Хочу поделиться реализацией пользовательского поля с типом "Привязка к складу" Битрикса. Тип создавался для HL-блоков
Структура файлов, участвующих в построении поля такая:
Сейчас разберем содержимое каждого файла. Я опущу их расположение в проекте, т.к. это не важно: разместить их можно в любом удобном месте (программисты поймут) Начнем с главного файла с классом, описывающим новый тип поля - CUserTypeStoreLink.php
<?php
namespace Project\UserTypes\StoreLink;
use Bitrix\Catalog\StoreTable;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Loader;
use Bitrix\Main\LoaderException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use CJSCore;
/**
* Class CUserTypeStoreLink
*
* Класс описывает пользовательское свойство привязки к складу
*
* @package Project\UserTypes\StoreLink
*/
class CUserTypeStoreLink
{
const USER_TYPE = "mercool_store_link";
/**
* Формируем массив значение
*
* @param $mxValue
* @return array
*/
private static function getValuesArray($mxValue)
{
$arValues = array();
if (!empty($mxValue)) {
if (is_array($mxValue)) {
$arValues = $mxValue;
} else {
$arValues = [$mxValue];
}
}
return $arValues;
}
/**
* Функция регистрируется в качестве обработчика события OnUserTypeBuildList
* @return array
*/
public function GetUserTypeDescription()
{
return [
"USER_TYPE_ID" => self::USER_TYPE,
"CLASS_NAME" => __CLASS__,
"DESCRIPTION" => "Привязка к складу",
"BASE_TYPE" => "int",
];
}
/**
* Функция вызывается при добавлении нового свойства для конструирования SQL запроса
* создания столбца значений свойства
*
* @param $arUserField
* @return string
*/
public function GetDBColumnType($arUserField)
{
$sNum = "";
switch (strtolower($GLOBALS["DB"]->type)) {
case "mysql":
$sNum = "int(18)";
break;
case "oracle":
$sNum = "number(18)";
break;
case "mssql":
$sNum = "int";
break;
}
return $sNum;
}
/**
* Функция вызывается перед сохранением метаданных (настроек) свойства в БД
*
* @param $arUserField
* @return array
*/
public function PrepareSettings($arUserField)
{
return [];
}
/**
* Функция вызывается при выводе формы метаданных (настроек) свойства
*
* @param bool $arUserField
* @param $arHtmlControl
* @param $bVarsFromForm - флаг отправки формы
* @return string - HTML для вывода
*/
public function GetSettingsHTML($arUserField = false, $arHtmlControl, $bVarsFromForm)
{
return "";
}
/**
* Функция валидатор значений свойства вызвается в $GLOBALS["USER_FIELD_MANAGER"]->CheckField()
* при добавлении/изменении
*
* @param $arUserField
* @param $value - начение для проверки на валидность
* @return array - массив массивов ("id","text") ошибок
*/
public function CheckFields($arUserField, $value)
{
return [];
}
/**
* Получение склада по ID
*
* @param int $iStoreId
* @return array
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
private static function getStore(int $iStoreId)
{
Loader::includeModule("sale");
$arFilter["=ACTIVE"] = "Y";
$arFilter["=ID"] = $iStoreId;
$arStore = StoreTable::getList(array(
"filter" => $arFilter,
"select" => array(
"ID",
"TITLE",
"ADDRESS"
),
"limit" => 1,
"cache" => array(
"ttl" => TTL,
"cache_joins" => true
)
))->fetch();
return self::makeStoreArray($arStore);
}
/**
* формируем нужный формат массива Склада
*
* @param array $arStore
* @return array
*/
public static function makeStoreArray(array $arStore)
{
return array(
"ID" => $arStore["ID"],
"TITLE" => $arStore["TITLE"],
"ADDRESS" => $arStore["ADDRESS"]
);
}
/**
* Формы редактирования значения свойства
*
* @param $arUserField
* @param $arHtmlControl
* @return string - HTML для вывода
*/
public function GetEditFormHTML($arUserField, $arHtmlControl)
{
self::registrExt();
return self::getItemFieldHtml($arHtmlControl["NAME"], $arHtmlControl["VALUE"]);
}
/**
* Вывод в форме редактирования множественного поля
*
* @param $arUserField
* @param $arHtmlControl
* @return string
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
public function GetEditFormHTMLMulty($arUserField, $arHtmlControl)
{
self::registrExt();
return self::getItemFieldHtml($arHtmlControl["NAME"], $arHtmlControl["VALUE"], true);
}
/**
* Генерируем HTML код экземпляра поля
*
* @param string $sFieldName
* @param $mxValue
* @param bool $bIsMultiple
* @return string
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
private function getItemFieldHtml(string $sFieldName, $mxValue, bool $bIsMultiple = false)
{
$sFieldName = str_replace('[]', '', $sFieldName);
$sPathFromContentRoot = str_replace($_SERVER["DOCUMENT_ROOT"], "", __DIR__);
$arValues = self::getValuesArray($mxValue);
list($sReturn, $iKey) = self::generateListHtml($arValues, $sFieldName, $bIsMultiple);
$sReturn .= "<div class='".self::USER_TYPE."-filter' data-code='".$sFieldName."' data-ajaxurl='".$sPathFromContentRoot."/ajax.php' >";
$sReturn .= "<input class='".self::USER_TYPE."' type='text' data-code='".$sFieldName."' name='".$sFieldName."_TITLE' value='' placeholder='Название'>";
$sReturn .= "<input class='".self::USER_TYPE."' type='text' data-code='".$sFieldName."' name='".$sFieldName."_ADDRESS' value='' placeholder='Адрес склада'>";
$sReturn .= "</div>";
$sReturn .= "<div class='store-search-result' data-count='".($iKey + 1)."' data-multiple='".($bIsMultiple ? "true" : "false")."' data-code='".$sFieldName."'></div>";
return $sReturn;
}
/**
* Функция вызывается при выводе фильтра на странице списка
*
* @param $arUserField
* @param $arHtmlControl
* @return string - HTML для вывода
*/
public function GetFilterHTML($arUserField, $arHtmlControl)
{
return self::GetEditFormHTML($arUserField, $arHtmlControl);
}
/**
* Функция должна вернуть представление значения поля для поиска
*
* @param $arUserField
* @return string - посковое содержимое
*/
public function OnSearchIndex($arUserField)
{
if (is_array($arUserField["VALUE"])) {
return implode("\r\n", $arUserField["VALUE"]);
} else {
return $arUserField["VALUE"];
}
}
/**
* Функция вызывается перед сохранением значений в БД
*
* @param $arUserField
* @param $iValue
* @return int - значение для вставки в БД
*/
public function OnBeforeSave($arUserField, $iValue)
{
if (intval($iValue) > 0) {
return intval($iValue);
}
}
/**
* Функция вызывается при выводе значения свойства в списке элементов
*
* @param $arUserField
* @param $arHtmlControl
* @return string - HTML для вывода
*/
public function GetAdminListViewHTML($arUserField, $arHtmlControl)
{
$sReturn = "";
if (!empty($arHtmlControl["VALUE"])) {
$arStore = self::getStore($arHtmlControl["VALUE"]);
$sReturn .= "<p>[".$arStore["ID"]."] ".$arStore["TITLE"]." | ".$arStore["ADDRESS"]."</p>";
}
return $sReturn;
}
/**
* Подключаем стили и скрипты к полю
*/
private function registrExt()
{
$sPathFromContentRoot = str_replace($_SERVER["DOCUMENT_ROOT"], "", __DIR__);
CJSCore::RegisterExt("store_link", [
"js" => $sPathFromContentRoot."/js/script.js",
"css" => $sPathFromContentRoot."/css/style.css",
"rel" => ["jquery"]
]);
CJSCore::Init(["jquery", "store_link"]);
}
/**
* Генерируем HTML списка значений
*
* @param array $arValues
* @param $sFieldName
* @param bool $bIsMultiple
* @return array
* @throws ArgumentException
* @throws LoaderException
* @throws ObjectPropertyException
* @throws SystemException
*/
private function generateListHtml(array $arValues, $sFieldName, bool $bIsMultiple): array
{
$iKey = 0;
$sReturn = "<ul class='".self::USER_TYPE."-list".(empty($arValues) ? " hidden" : "")."' data-code='".$sFieldName."'>";
if (!empty($arValues)) {
foreach ($arValues as $iKey => $sVal) {
if ($arStore = self::getStore($sVal)) {
$sReturn .= "<li>[".$arStore["ID"]."] ".$arStore["TITLE"]." | ".
$arStore["ADDRESS"]." <a class=\"delete\" href=\"#\">[удалить]</a>";
if ($bIsMultiple) {
$sReturn .= "<input type='hidden' name='".$sFieldName."[".$iKey."]' value='".(!empty($sVal) ? $sVal : "")."'></li>";
} else {
$sReturn .= "<input type='hidden' name='".$sFieldName."' value='".(!empty($sVal) ? $sVal : "")."'></li>";
}
}
}
}
$sReturn .= "</ul>";
return array($sReturn, $iKey);
}
}
Главная особенность - формирование списка значений и фильтра для поиска. Обратите внимание на метод getItemFieldHtml. Именно в нем формируется HTML с фильтром и выводом значений
AJAX запросы обрабатывает файл ajax.php. В нем содержится класс CUserTypeStoreLinkAction по обработке AJAX запросов на формирование списка вариантов складов, удовольтворяющих условиям фильтра. Его код представлен ниже:
<?
namespace Project\UserTypes\StoreLink;
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
use Bitrix\Catalog\StoreTable;
use Bitrix\Main\ArgumentException;
use Bitrix\Main\Loader;
use Bitrix\Main\Application;
use Bitrix\Main\LoaderException;
use Bitrix\Main\ObjectPropertyException;
use Bitrix\Main\SystemException;
use Bitrix\Main\Web\Json;
final class CUserTypeStoreLinkAction
{
private $sTitle;
private $sAddress;
/**
* CUserTypeStoreLinkAction constructor.
* @throws LoaderException
* @throws SystemException
*/
public function __construct()
{
Loader::includeModule("sale");
$obRequest = Application::getInstance()->getContext()->getRequest();
$this->sTitle = $obRequest->get("title");
$this->sAddress = $obRequest->get("address");
}
/**
* Получить список складов
*
* @return array
* @throws SystemException
* @throws ArgumentException
* @throws ObjectPropertyException
*/
public function getStores()
{
$arStores = array();
$arFilter["=ACTIVE"] = "Y";
if (!empty($this->sTitle)) {
$arFilter["%TITLE"] = $this->sTitle;
}
if (!empty($this->sAddress)) {
$arFilter["%ADDRESS"] = $this->sAddress;
}
$rsStores = StoreTable::getList(array(
"filter" => $arFilter,
"select" => array(
"ID",
"TITLE",
"ADDRESS"
),
"cache" => array(
"ttl" => TTL,
"cache_joins" => true
)
));
while ($arStore = $rsStores->fetch()) {
$arStores[] = CUserTypeStoreLink::makeStoreArray($arStore);
}
return $arStores;
}
}
header("Content-Type: application/json");
$obUserTypeStoreLinkAction = new CUserTypeStoreLinkAction;
$arStores = $obUserTypeStoreLinkAction->getStores();
echo Json::encode($arStores);
?>
Весь JS расположен в файле script.js
$(document).on("click", ".hover-variant", function () {
var sFieldCode = $(this).attr("data-code");
var iStoreId = $(this).attr("data-id");
var obList = $("ul[data-code='" + sFieldCode + "']");
var obSearchResults = $(".store-search-result[data-code='" + sFieldCode + "']");
var bIsMultilple = (obSearchResults.attr("data-multiple") === "true");
var sHiddenValueField = "<input type='hidden' name='" + sFieldCode + "' value='" + iStoreId + "' >";
var iKey = Number.parseInt($(this).parent().attr("data-count"));
var obFilter = $(".mercool_store_link-filter[data-code='" + sFieldCode + "']");
if (bIsMultilple) {
sHiddenValueField = "<input type='hidden' name='" + sFieldCode + "[" + iKey + "]' value='" + iStoreId + "' >";
}
var sValue = "<li>" + $(this).html().replace(/<[^>]+>/g, '') + sHiddenValueField + " <a class='delete' href='#'>[удалить]</a></li>";
if (bIsMultilple) {
obList.append(sValue);
$(this).parent().attr("data-count", iKey + 1);
} else {
obList.html(sValue);
}
obList.removeClass("hidden");
obSearchResults.hide();
obFilter.find("input").val("");
});
$(document).on("click", ".mercool_store_link-list .delete", function () {
var obListItem = $(this).parent();
obListItem.hide();
obListItem.find("input").val("");
return false;
});
/**
* AJAX-поиск на сервере
*
* @param {string} sFieldCode
*/
var searchStores = function (sFieldCode) {
var obSearchResults = $(".store-search-result[data-code='" + sFieldCode + "']");
var sTitle = $('input[name="' + sFieldCode + '_' + 'TITLE' + '"').val();
var sAddress = $('input[name="' + sFieldCode + '_' + 'ADDRESS' + '"').val();
var sAjaxUrl = $(".mercool_store_link-filter[data-code='" + sFieldCode + "']").attr("data-ajaxurl");
obSearchResults.html("Загрузка...");
obSearchResults.show();
BX.ajax.post(
sAjaxUrl,
{
"title": sTitle,
"address": sAddress
},
function (obData) {
obData = JSON.parse(obData);
var sVariants = "";
$.each(obData, function (iIndex, obStore) {
sVariants = sVariants + "<div class='hover-variant' data-code='" + sFieldCode + "' data-id='" + obStore.ID + "'>[" + obStore.ID + "] " + displayMatches(obStore.TITLE, sTitle) + " | " + displayMatches(obStore.ADDRESS, sAddress) + "</div>";
});
if (sVariants.length == 0) {
sVariants = "Ничего не найдено. Попробуйте изменить параметры поиска.";
}
obSearchResults.html(sVariants);
obSearchResults.removeClass("preload");
}
);
}
/**
* Отложенный вызов
*/
var delayExec = (function () {
var iTimer = 0;
return function (callback, ms) {
clearTimeout(iTimer);
iTimer = setTimeout(callback, ms);
};
})();
$(document).on("keyup", ".mercool_store_link", function () {
if ($(this).val().length >= 3) {
var sFieldCode = $(this).attr("data-code");
delayExec(function () {
searchStores(sFieldCode);
}, 600);
}
});
/**
* Выделение подстроки в строке
*
* @param {string} sFullStr
* @param {string} sSearchSubStr
*/
var displayMatches = function (sFullStr, sSearchSubStr) {
var obRegex = new RegExp(sSearchSubStr, 'gi')
var sStr = sFullStr.replace(obRegex, function (sSearchSubStr) {
return "<b>" + sSearchSubStr + "</b>"
})
return sStr;
}
Файл в основном шлет запросы на ajax.php поиск складов и формирует список вариантов. А также небольшие правки для удобства пользования
Есть файл style.css со стилями для поля. Содержит минимальный набор стилей для корректной работы
Всем привет! На днях столкнулся с необходимостью вывода товаров из нескольких Инфоблоков с возможностью сортировки и фильтрации (в моем случаем фильтрации по цене). Хочу поделиться кодом.
class CIBlockElementMulti
{
/**
* Функция для получения товаров из нескольких инфоблоков с получением и сортировкой свойств
* Функциональность ограничена, в частных случаях возможно потребует доработки
*
* @param array $arOrder
* @param array $arFilter
* @param bool|array $arGroupBy
* @param bool|array $arNavStartParams
* @param array $arSelectFields
*
* @return integer|CIBlockResult
*/
public function GetList(
$arOrder = ["SORT" => "ASC"],
$arFilter = [],
$arGroupBy = false,
$arNavStartParams = false,
$arSelectFields = []
) {
global $DB;
$sWhere = "";
if (!is_array($arFilter["IBLOCK_ID"])) {
return false;
}
$sSql = "
SEL ECT
BE.ID AS ID,
BE.IBLOCK_ID AS IBLOCK_ID,
B.CODE AS IBLOCK_CODE,
BE.IBLOCK_SECTION_ID AS IBLOCK_SECTION_ID,
";
$sPropSql = "";
foreach ($arSelectFields as $arField) {
if (strpos($arField, "PROPERTY_") === false) {
continue;
}
$arProps = [];
foreach ($arFilter["IBLOCK_ID"] as $iIblockId) {
$dbRes = \CIBlockProperty::GetList(false, [
"ACTIVE" => "Y",
"IBLOCK_ID" => $iIblockId,
"CODE" => str_replace("PROPERTY_", "", $arField),
]);
if ($arProp = $dbRes->GetNext()) {
$arProps[] = "FPS".$iIblockId.".PROPERTY_".$arProp["ID"];
}
}
if (!$arProps) {
$arProps [] ="''";
}
$sPropSql .= " coalesce(".join(",", $arProps).") AS ".$arField."_VALUE, \r\n";
}
$sSql .= $sPropSql;
foreach ($arFilter as $sKey => $arValue) {
if (strpos($sKey, ">CATALOG_PRICE") !== false) {
$iPriceId = explode("CATALOG_PRICE_", $sKey)[1];
$sWhere .= " AND ((((CAT_P".$iPriceId.".PRICE > '".$arValue."'))))";
}elseif (is_numeric($sKey) and is_array($arValue)) {
if (isset($arValue["LOGIC"])) {
$sLogic = $arValue["LOGIC"];
unset($arValue["LOGIC"]);
$arTmp = [];
foreach ($arValue as $arVal2) {
$sKey2 = key($arVal2);
$arVal2 = reset($arVal2);
if (strpos($sKey2, "CATALOG_PRICE") !== false) {
$iPriceId = explode("CATALOG_PRICE_", $sKey2)[1];
$arTmp[] = "((((CAT_P".$iPriceId.".PRICE BETWEEN '".$arVal2[0]."' AND '".$arVal2[1]."'))))";
}
}
if ($arTmp) {
$sWhere .= " AND (".join(" ".$sLogic." ", $arTmp).") ";
}
}
} elseif ($sKey == "IBLOCK_ID" || $sKey == "ID") {
if (is_array($arValue)) {
$iIds = join("','", $arValue);
} else {
$iIds = $arValue;
}
$sWhere.=" AND BE.".$sKey." IN ('".$iIds."')";
} elseif ($sKey == "ACTIVE") {
$sWhere .= " AND BE.ACTIVE = '".$arFilter["ACTIVE"]."'";
}
}
if ($iPriceId) {
$sSql .= "
CAT_P".$iPriceId.".ID AS CATALOG_PRICE_ID_".$iPriceId.",
CAT_P".$iPriceId.".CATALOG_GROUP_ID AS CATALOG_GROUP_ID_".$iPriceId.",
CAT_P".$iPriceId.".PRICE AS CATALOG_PRICE_".$iPriceId.",
CAT_P".$iPriceId.".CURRENCY AS CATALOG_CURRENCY_".$iPriceId.",
CAT_P".$iPriceId.".QUANTITY_FROM AS CATALOG_QUANTITY_FROM_".$iPriceId.",
CAT_P".$iPriceId.".QUANTITY_TO AS CATALOG_QUANTITY_TO_".$iPriceId.",
'Y' AS CATALOG_CAN_ACCESS_".$iPriceId.",
'Y' AS CATALOG_CAN_BUY_".$iPriceId.",
CAT_P".$iPriceId.".EXTRA_ID AS CATALOG_EXTRA_ID_".$iPriceId.", \r\n";
}
$sSql .= "
CAT_PR.QUANTITY AS CATALOG_QUANTITY,
CAT_PR.QUANTITY_RESERVED AS CATALOG_QUANTITY_RESERVED,
IF (CAT_PR.QUANTITY_TRACE = 'D', 'N', CAT_PR.QUANTITY_TRACE) AS CATALOG_QUANTITY_TRACE,
CAT_PR.QUANTITY_TRACE AS CATALOG_QUANTITY_TRACE_ORIG,
IF (CAT_PR.CAN_BUY_ZERO = 'D', 'Y', CAT_PR.CAN_BUY_ZERO) AS CATALOG_CAN_BUY_ZERO,
CAT_PR.CAN_BUY_ZERO AS CATALOG_CAN_BUY_ZERO_ORIG,
IF (CAT_PR.NEGATIVE_AMOUNT_TRACE = 'D', 'Y', CAT_PR.NEGATIVE_AMOUNT_TRACE) AS CATALOG_NEGATIVE_AMOUNT_TRACE,
CAT_PR.NEGATIVE_AMOUNT_TRACE AS CATALOG_NEGATIVE_AMOUNT_ORIG,
IF (CAT_PR.SUBSCRIBE = 'D', 'Y', CAT_PR.SUBSCRIBE) AS CATALOG_SUBSCRIBE,
CAT_PR.SUBSCRIBE AS CATALOG_SUBSCRIBE_ORIG,
CAT_PR.AVAILABLE AS CATALOG_AVAILABLE,
CAT_PR.WEIGHT AS CATALOG_WEIGHT,
CAT_PR.WIDTH AS CATALOG_WIDTH,
CAT_PR.LENGTH AS CATALOG_LENGTH,
CAT_PR.HEIGHT AS CATALOG_HEIGHT,
CAT_PR.MEASURE AS CATALOG_MEASURE,
CAT_VAT.RATE AS CATALOG_VAT,
CAT_PR.VAT_ID AS CATALOG_VAT_ID,
CAT_PR.VAT_INCLUDED AS CATALOG_VAT_INCLUDED,
CAT_PR.PRICE_TYPE AS CATALOG_PRICE_TYPE,
CAT_PR.RECUR_SCHEME_TYPE AS CATALOG_RECUR_SCHEME_TYPE,
CAT_PR.RECUR_SCHEME_LENGTH AS CATALOG_RECUR_SCHEME_LENGTH,
CAT_PR.TRIAL_PRICE_ID AS CATALOG_TRIAL_PRICE_ID,
CAT_PR.WITHOUT_ORDER AS CATALOG_WITHOUT_ORDER,
CAT_PR.SELECT_BEST_PRICE AS CATALOG_SELECT_BEST_PRICE,
CAT_PR.PURCHASING_PRICE AS CATALOG_PURCHASING_PRICE,
CAT_PR.PURCHASING_CURRENCY AS CATALOG_PURCHASING_CURRENCY,
CAT_PR.TYPE AS CATALOG_TYPE,
CAT_PR.BUNDLE AS CATALOG_BUNDLE
FR OM
b_iblock B
INNER JOIN b_lang L ON B.LID = L.LID
INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
";
foreach ($arFilter["IBLOCK_ID"] as $iIblockId) {
$sSql .= "
LEFT JOIN b_iblock_element_prop_s".$iIblockId." FPS".$iIblockId." ON FPS".$iIblockId.".IBLOCK_ELEMENT_ID = BE.ID \r\n";
}
if ($iPriceId) {
$sSql .= "
LEFT JOIN b_catalog_price CAT_P".$iPriceId." ON(CAT_P".$iPriceId.".PRODUCT_ID = BE.ID AND CAT_P".$iPriceId.".CATALOG_GROUP_ID = ".$iPriceId.") \r\n";
}
$sSql .= "
LEFT JOIN b_catalog_product CAT_PR ON(CAT_PR.ID = BE.ID)
LEFT JOIN b_catalog_iblock CAT_IB
ON((CAT_PR.VAT_ID IS null OR CAT_PR.VAT_ID = 0) AND CAT_IB.IBLOCK_ID = BE.IBLOCK_ID)
LEFT JOIN b_catalog_vat CAT_VAT
ON(CAT_VAT.ID = IF ((CAT_PR.VAT_ID IS null OR CAT_PR.VAT_ID = 0), CAT_IB.VAT_ID, CAT_PR.VAT_ID))
WHERE 1 = 1
".$sWhere."\r\n";
if (!$arFilter["IBLOCK_ID_FULL_LIST"] and is_array($arFilter["IBLOCK_ID"]) and sizeof($arFilter["IBLOCK_ID"])>1)
$sPreOrder = "IBLOCK_ID asc, ";
if (strpos(key($arOrder), "PROPERTY_") !== false) {
$sOrderBy = "ORDER BY ".$sPreOrder.key($arOrder)."_VALUE ".reset($arOrder);
} elseif (stristr(key($arOrder),"CATALOG")) {
$sOrderBy = "ORDER BY ".$sPreOrder.key($arOrder)." ".reset($arOrder);
} else {
$sOrderBy = "ORDER BY ".$sPreOrder."BE.".key($arOrder)." ".reset($arOrder);
}
if ($sOrderBy) {
$sOrderBy .= ", ID DESC";
}
$sSql .= "
AND (((BE.WF_STATUS_ID = 1 AND BE.WF_PARENT_ELEMENT_ID IS null)))
".$sOrderBy;
$obResCnt = $DB->Query($sSql, false, __LINE__);
$iCnt = $obResCnt->SelectedRowsCount();
$obRes = new \CDBResult();
$obRes->NavQuery($sSql, $iCnt, $arNavStartParams);
$obRes = new \CIBlockResult($obRes);
return $obRes;
}
}
Жуков Евгений написал: Проблема в сортировке или фильтрации по свойствам (их получение - вопрос давно решенный, даже с учетом постранички) для инфоблоков 2.0
Жуков Евгений, По цене возможно да. А вот по свойству - нет. Ибо цитирую документацию: "Для инфоблоков 2.0 такая выборка будет работать только в том случае, если в ней не запрашиваются свойства элементов."
Хочу поделиться полезнейшим событием из ядра d7 - "События после сохранения сущности" , которое здорово помогло при решении моей проблемы. Но обо всём по-подробнее.
Имеется сайт, который принимает онлайн оплату Сбербанка. При этом клиент может оплатить как часть заказа, так и полную стоимость. Факт оплаты мы должны отправлять на сторонний сервис. Казалось бы всё просто: воспользуйтесь событием OnSalePayOrder из старого ядра. Сказано - сделано!
И тут начались проблемы: почему-то часть оплат отправляются на сторонний сервис, а часть - нет. Стали копать, и выяснили: оказывается событие OnSalePayOrder срабатывает только при полной оплате стоимости заказа. Стали думать-размышлять, гуглить, и тут наткнулись на замечательное событие из ядра d7OnSalePaymentEntitySaved.
Данное событие срабатывает после сохранения сущности оплаты, и не важно: оплачена ли полная стоимость заказа или частичная. Отлавливаем факт оплаты "PAID" = "Y" и отправляем данные. Ниже приведу небольшие фрагменты кода:
1. Регистрируем события для нашего модуля интеграций
2. Пишем метод обработчик. В моём случае я дополнительно получаю данные по заказу, и далее, в зависимости от выбранной платежной системы (помимо Сбербанка используется и некоторые другие), создаём специальный объект класса под каждую платежную систему, который выполняет функции отправки данных в сервис.
Спасибо! Подскажите, как "отлавливать" ответ факта оплаты на стороннем сервисе платежной системы? Предполагаю что сторонний сервис "оповещает" сайт, передает ему параметры какие то? Но куда, на какую страницу он их передает, как их потом принимать, анализировать и выполнять дальнейшие действия (например смена статуса заказа и т.п)
Хочу поделиться способом подключать языковые файлы для JS. Узнал я о нем от своего наставника Владислава, так что огромная ему благодарность.
Все знают, что у Битрикса есть JS метод BX.message() (https://dev.1c-bitrix.ru/api_help/js_l...essage.php), но к сожалению примеров использования в документации нет. Главный вопрос: Как "скормить" ему языковые файлы.
Рассмотрим на примере. Есть компонент с шаблоном, в котором создан script.js. Требуется реализовать мультиязычность в этом скрипте.
1) Создаём языковой файл script.js.php по аналогии с template.php
2) Создаём файл component_epilog.php со следующим содержимым:
use Bitrix\Main\Page\Asset,
Bitrix\Main\Localization\Loc;
// Получаем содержимое языкового файла
$ARJSMESS = Loc::LoadLanguageFile($_SERVER["DOCUMENT_ROOT"].$templateFolder."/script.js.php");
if (!empty($ARJSMESS)) {
// Добавляем объект с переводами.
Asset::getInstance()->AddString("<sc ript type=\"text/javascript\">BX.message(".CUtil::PhpToJSObject($ARJSMESS).")</sc ript>");
}
3) Используем метод BX.message() в самом script.js
Предложенный вариант подойдет и для использования в шаблонах сайтов.
Мне понравилось что вы подключаете лэнги ДЛЯ файла, которого не существует (sale.order.ajax/.default/script.js.php). Я думал там есть проверка, но видимо нет.
Также делает битрикс, только в template.php, а не в component_epilog.php.
Скажите, а чем плохо сделать то же самое в template.php ? В таком случае сгенереный массив с ленгами попадёт в кеш, а не будет отрабатывать на каждом хите. Или тут есть какие-то нюансы?
Кстати, скорее всего вместо CUtil::PhpToJSObject можно использовать \Bitrix\Main\Web\Json::encode. Не уверен, что они полностью совместимы.
Подскажите, пожалуйста, как создать через API Службу доставки по группам местоположений и Ограничения к ней по Местоположению (указать регионы, где её использовать).
Создать саму службу доставки не трудно - делаю через \Bitrix\Sale\Delivery\Services\Manager::add
А вот как добавить ограничение на конкретные местоположения? В "недрах" ядра нашел код, который Сначала добавляет ограничение, а потом напрямую в БД пишет записи с конкретными ограничениями:
$obCon = \Bitrix\Main\Application::getConnection(); $obCon->queryExecute('INS ERT IN TO b_sale_delivery2location (DELIVERY_ID, LOCATION_CODE, LOCATION_TYPE) VALUES ("'.$iDeliveryId.'", "'.$sLocationCode.'", "L");');
можно так $arLocation = ["L" => ["0000028032","0000028044"]]; // L - для местоположений , G - для типов местоположений Bitrix\Sale\Delivery\DeliveryLocationTable::resetMultipleForOwner($arDelivery["ID"], $arLocation);
Группы на сайте создаются не только сотрудниками «1С-Битрикс», но и партнерами компании. Поэтому мнения участников групп могут не совпадать с позицией компании «1С-Битрикс».