Bitrix Orm — легко и без геммороя ) подробно на github
https://github.com/sm1le404/bitrix_orm/tree/master
Автор: a.sirbiladze
Кастомное добавление товаров в админке для заказов Bitrix
Речь в статье пойдет о создании своего интерфейса для добавления товара в админке битрикса.
Обозначим основные моменты:
1) добавление кнопки
public static function initStartComplect()
{
return [
"BLOCKSET" => __METHOD__,
"getScripts" => [static::class, "initComplectScripts"],
];
}
public static function initComplectScripts($args)
{
Asset::getInstance()->addJs('https://cdnjs.cloudflare.com/ajax/libs/jquery/1.10.2/jquery.min.js');
$result = '
<скрипт>
setTimeout(function() {
$("[data-id=basket]").find(".adm-s-gray-title-btn-container").append("Добавить комплект");
}, 1000);
function addComplect(params)
{
var funcName = this.objName+\'.getParamsByProductId\';
window[funcName] = BX.proxy(function(params, iblockId){this.getParamsByProductId(params, iblockId);}, this);
var popup = new BX.CDialog({
content_url: \'/local/tools/comp_search_dialog.php?\'+
\'lang=\'+BX.Sale.Admin.OrderEditPage.languageId+
\'&LID=\'+BX.Sale.Admin.OrderEditPage.siteId+
\'&caller=order_edit\'+
\'&func_name=\'+funcName+
\'&STORE_FROM_ID=0\',
height: Math.max(500, window.innerHeight-400),
width: Math.max(800, window.innerWidth-400),
draggable: true,
resizable: true,
min_height: 500,
min_width: 800
});
BX.addCustomEvent(popup, \'onWindowRegister\', BX.defer(function(){
popup.Get().style.position = \'fixed\';
popup.Get().style.top = (parseInt(popup.Get().style.top) - BX.GetWindowScrollPos().scrollTop) + \'px\';
}));
popup.Show();
};
<скрипт>
';
return $result;
}
функция addComplect по сути дублирует стандартный вызов для добавления товара — за исключением что ссылаемся на свой скрипт в котором будет выбор товаров
2) Страница (скрипт) на котором будет расположена выборка выглядит примерно так
4) как передать товар из нашего списка обратно в заказ?
function selectProduct(productId, productName, iblockId, type) { BX.Sale.Admin.OrderBasketObj.getParamsByProductId( { full_quantity: "", id: productId, measure: "шт", measureRatio: "1", name: productName, quantity: "1", type: type, //4 - для ТП , 1 - для товаров }, iblockId ); return false; }
функцию вешаем на клик по товару к примеру и параметры соотвественно подтягиваем из него, остальная информация необходимая для добавление в корзину соберется сама.
Надеюсь это кому то пригодится.
Кастомные ошибки при создании/редактировании заказа в /bitrix/admin/
Зачастую мы сталкиваемся с задачами бизнеса которые диктуют свои правила.
К примеру дабы не допустить возможность ошибки со стороны менеджера оформляющего заказ на сайте, вводятся кастомные ошибки уведомляющие его о том что одно или несколько полей были описаны не верно, как этого добиться?
В D7 существует следующий способ:
use Bitrix\Main\Event; use App\Events\Order; $eventManager = EventManager::getInstance(); $eventManager->addEventHandler('sale', 'OnSaleOrderBeforeSaved', [Order::class, 'BeforeOrderUpdate']);
Листинг класса Order
namespace App\Events; use \Bitrix\Main; class Order { public static function BeforeOrderUpdate(Main\Event $event, $values) { if (Main\Context::getCurrent()->getRequest()->isAdminSection()) { /** @var \Bitrix\Sale\Order $order */ $order = $event->getParameter("ENTITY"); $isNew = $event->getParameter("IS_NEW"); $oldFields = $event->getParameter("VALUES"); if ($oldFields['STATUS_ID']) { return self::returnResultError('Недостаточно прав для изменения статуса заказа.', 'SALE_EVENT_ON_BEFORE_ORDER_SAVED_ERROR'); } } } public static function returnResultError($message, $code = '') { return new \Bitrix\Main\EventResult( \Bitrix\Main\EventResult::ERROR, \Bitrix\Sale\ResultError::create(new \Bitrix\Main\Error($message, $code)) ); } }
Переопределение ссылки на поиск пользователя при создании заказа через административный интерфейс.
Давно не писал всяких полезностей и вот случилось недавно разрешить проблему в админке Bitrix, надо было переопределелить стандартную функцию зашитую в ядро, лезть куда конечно же моветон.
Функции обладают возможностью для переопределения поведения. Переопределение происходит с помощью присвоения анонимной функции переменной, которая называется так же, как и переопределяемая функция.
Обработчик навесил на события OnAdminSaleOrderEditDraggable, OnAdminSaleOrderCreateDraggable.
return array("BLOCKSET" => "OrderEdit",
"getScripts" => array('\Events\OrderEdit', "orderScripts"),
);
Сам листинг скрипта
var oldfunc = BX.Sale.Admin.OrderBuyer.showChooseBuyerWindow;
BX.Sale.Admin.OrderBuyer.showChooseBuyerWindow = function (languageId) {
var currentWindow = window.open(
'/local/tools/user_search.php?sessid='+BX.bitrix_sessid()+'&lang='+languageId+'&FN='
+BX.Sale.Admin.OrderEditPage.formId+'&FC=USER_ID',
'',
'scrollbars=yes,resizable=yes,width=840,height=500,top='+Math.floor((screen.height - 840)/2-14)+',left='+Math.floor((screen.width - 760)/2-5)
);
currentWindow.onunload = function(){
setTimeout(function() {
BX.Sale.Admin.OrderAjaxer.sendRequest(
BX.Sale.Admin.OrderEditPage.ajaxRequests.refreshOrderData(),true);
}, 1000);
}
};
//тут к примеру можно вызвать oldfunc, если нужен старый функционал
showChooseBuyerWindow — содержимое воспроизводит родителя, за исключением ссылки на скрипт поиска пользователя, таким образом можно преобразовать любой интересующий вас js представленный в админке, при этом не испытывая особых затруднений при обновлении и расширить стандартный функционал.
Задачи поддержки. Постановка.
Зачастую в работе техподдержки существуют проблемы с постановкой задач от клиентов и трансляции онных разработчику, в чем это выражается?
Клиент: У нас ничего не работает! решите проблему!
Менеджер: Транслирует задачу разработчику — со слов заказчика, не добавляя никакой дополнительной информации.
Разработчик: Отбрасывает задачу обратно в менеджера для уточнения что именно не работает.
Менеджер: Пишет заказчику с уточняющими вопросами
….
цикл может продолжаться неограниченное число итераций до получения информации которая удовлетворит требованиям разработчика.
Как этого избежать?
Необходимо задать всего 3 простых вопроса заказчику:
1) Что делаем?
2) Что получаем?
3) Что ожидаем?
Пример
1) Нажимаем на кнопку
2) Открывается popup
3) Переходим по ссылке
Благодаря этой достаточно простой методике можно сократить время на решение и постановку задачи до приемлимых сроков.
Highloadblock теперь ORM
Как сделать работу с Highloadblock bitrix более сносной? Решение есть, описание сущности таблицы в ORM, данный способ позволит нам с легкостью обращаться к полям справочника как к любой другой таблице описанной в ORM, те доступны все методы Add, Update, GetList и т.д.
Только существует один ньюанс при обращении к полю множественного типа оно возращается строкой для которой надо применить unseriallize().
Подключить данный код можно в php_interface/init.php
Рекомендация — для каждой таблицы создавать отдельный файл с описанием сущности справочника
<?php namespace Gw\Tables; use Bitrix\Main\Entity\DataManager, Bitrix\Highloadblock as HL, Bitrix\Main\Entity\IntegerField, Bitrix\Main\Entity\TextField, Bitrix\Main\Entity\FloatField, Bitrix\Main\Loader, Bitrix\Highloadblock\HighloadBlockTable; Loader::includeModule('highloadblock'); /** * Class HlCityTable * @package Gw\Tables */ class HlStatusTable extends DataManager { private static $id = 1; public static function getTableName() { return 'hl_order_status'; } public static function getMap() { return array( 'ID' => new IntegerField('ID', array( 'primary' => true, 'title' => "ID" )), 'UF_ID' => new TextField('UF_ID', array( 'title' => "UF_ID" )), 'UF_COLOR' => new TextField('UF_COLOR', array( 'title' => "UF_COLOR" )), ); } }
Модуль геолокации
Возникла необходимость использовать местоположения по нескольким странам, которые отсутствуют в редакции «Стандарт», а так же в недостаточном количестве представлены в более продвинутых редакциях битрикса с учетом языковых зависимостей.
Для этого собрал модуль — https://github.com/sm1le404/ds.geolocation, на основе https://github.com/x88/i18nGeoNamesDB этой геобазы.
В настоящий момент таблицы пока пусты, отдельно надо будет сделать прогрузку так как битрикс не успевает за приемлимое время установки все протянуть.
В ближайшее время ждите обновлений.
Как добавить комментарий к задаче на КП Bitrix
Возникла задача — необходимо добавить комментарий из письма комментарием к задаче, то есть владелец задачи может оставить комментарий к своей задачи на прямую из почты.
Расписывать каким образом читать почту я не буду, просто приведу листинг того как добавить комментарий и как хотелось бы на D7 , но пока нельзя.
$task = \Bitrix\Tasks\Internals\TaskTable::getByPrimary($res['TASK_ID'])->fetch(); $forumID = \CForumTopic::GetByID($task['FORUM_TOPIC_ID']); $comment = ''; $postFields = array( 'TOPIC_ID' => $task['FORUM_TOPIC_ID'], // идентификатор topic'a 'FORUM_ID' => $forumID['FORUM_ID'], // id форума, приходится делать отдельным запросом 'SOURCE_ID' => 'WEB', 'USE_SMILES' => 'Y', 'NEW_TOPIC' => 'Y', 'POST_MESSAGE' => $comment, 'POST_MESSAGE_HTML' => $comment, 'AUTHOR_ID' => $user['ID'], 'AUTHOR_NAME' => $user['NAME'].' '.$user['LAST_NAME'], // без этого поля тоже не добавит 'XML_ID' => 'TASK_'.$task['ID'], // важно соблюдать данный формат ); $result = \CForumMessage::Add($postFields); if (!$result) { GLOBAL $APPLICATION; $ex = $APPLICATION->GetException(); echo $ex->GetString(); }
А вот как хотелось бы на D7, но висит Exception в классе Сообщений (спасибо битрикс)
$result = \Bitrix\Forum\MessageTable::add(array( 'TOPIC_ID' => $task['FORUM_TOPIC_ID'], 'FORUM_ID' => $forumID['FORUM_ID'], 'SOURCE_ID' => 'WEB', 'USE_SMILES' => 'Y', 'NEW_TOPIC' => 'Y', 'POST_MESSAGE' => $comment, 'POST_MESSAGE_HTML' => $comment, 'AUTHOR_ID' => $user['ID'], 'XML_ID' => 'TASK_'.$task['ID'] )); print_r($result->getErrorMessages());
Удаление старых резервных копий
Столкнулся с проблемой следующего характера — при настройке стандартного резервного копирования в облаке Битрикс и установке -> удалять в случае если кол-во копий больше N, если копия не ушла в облако по каким то причинам, она остается на сервере, что в свою очередь захломляет пространство. Чтобы обойти этот момент — написал простой агент, который можно вешать раз в день, он будет в свою очередь удалять лишние копии, выбирает конечно же самые старые.
function removeBackup() { define('DOCUMENT_ROOT', rtrim(str_replace('\\','/',$_SERVER['DOCUMENT_ROOT']),'/')); define("BX_ROOT", "/bitrix"); $arFiles = array(); $arTmpFiles = array(); if (is_dir($p = DOCUMENT_ROOT.BX_ROOT.'/backup')) { if ($dir = opendir($p)) { while(($item = readdir($dir)) !== false) { $f = $p.'/'.$item; if (!is_file($f)) continue; $arTmpFiles[] = array( 'NAME' => $item, 'SIZE' => filesize($f), 'DATE' => filemtime($f), 'BUCKET_ID' => 0, 'PLACE' => GetMessage("MAIN_DUMP_LOCAL") ); } closedir($dir); } } $arParts = array(); $arSize = array(); $arDates = array();$arDates['test'] = 'test'; $i=0; foreach($arTmpFiles as $k=>$ar) { if (preg_match('#^(.*\.(enc|tar|gz|sql))(\.[0-9]+)?$#',$ar['NAME'],$regs)) { $i++; $BUCKET_ID = intval($ar['BUCKET_ID']); $arParts[$BUCKET_ID.$regs[1]]++; $arSize[$BUCKET_ID.$regs[1]] += $ar['SIZE']; if (!$regs[3]) { if ($by == 'size') $key = $arSize[$BUCKET_ID.$regs[1]]; elseif ($by == 'timestamp') $key = $ar['DATE']; elseif ($by == 'location') $key = $ar['PLACE']; else // name $key = $regs[1]; $key .= '_'.$i; $arFiles[$key] = $ar; $arDates[$key] = $ar['DATE']; } } } $state = 2; // количество копий которое должно остаться asort($arDates); // Отсортировали по дате в порядке возрастанаия вверху самые старые if (count($arFiles) > $state) { foreach ($arDates as $dumpKey => $date) { $founded = $arFiles[$dumpKey]; if (!empty($founded['NAME'])) { foreach ($arParts as $key => $val) { if (strstr($key, $founded['NAME']) !== false) { $partsCount = $val; break; } } $backPath = DOCUMENT_ROOT.BX_ROOT.'/backup/'; for ($i = 1; $i < $partsCount; $i++) { $filePath = $backPath.$founded['NAME'].'.'.$i; if (file_exists($filePath)) { unlink($filePath); } } unlink($backPath.$founded['NAME']); } } } return 'removeBackup();'; }
Bitrix КП добавление файла к задаче
Возникла задача сохранения файла из внешнего источника в задаче КП Битрикса, но оказалось все не совсем просто, в связи с тем что использует он модуль disk, а не только стандартный класс CFile, чтобы решить данную проблему пришлось покопать исходники.
$fileInfo = \CFile::GetFileArray(1); $folder = \Bitrix\Disk\Folder::getById(1); $data = array( 'NAME' => $fileID.'_'.$fileInfo['ORIGINAL_NAME'], 'STORAGE_ID' => 1, 'FILE_ID' => $fileInfo['ID'], 'CREATED_BY' => 1, 'SIZE' => $fileInfo['FILE_SIZE'], 'PARENT' => $folder, 'PARENT_ID' => 1 ); $error = new \Bitrix\Disk\Internals\Error\ErrorCollection; $res = \Bitrix\Disk\File::add($data, $error); $fileDiskID = $res->getId();
Список всех папок на диске для сохранения можно получить так же —
\Bitrix\Disk\Internals\FolderTable::getList();
здесь есть для PARENT_ID и STORAGE_ID
Добавляем файл к задаче
if ($fileID) { $newFiles[] = \Bitrix\Disk\Uf\FileUserType::NEW_FILE_PREFIX.$fileID; } if (count($newFiles)) { $USER_FIELD_MANAGER->Update("TASKS_TASK", $taskID, array( 'UF_TASK_WEBDAV_FILES' => $newFiles, )); }