Уже достаточно давно у битрикса появился новый тип свойств инфоблоков, "Справочник", основанный на привязке
элементов инфоблока к элементам хайлоад-инфоблоков. Как известно, данный вид свойств имеет связку не по ИД значения
справочника, а по полю "UF_XML_ID". Иногда может возникнуть ситуация, когда в справочнике дублируются значения с
одинаковым внешним кодом (при не очень грамотно построенном механизме добавления новых значений в справочник). Тогда
на помощь в очистке справочника может прити данный скрипт.
Я для каждого проекта создаю отдельный модуль, в который помещаю все функции и константы проекта, поэтому в коде будут появляться некоторые методы абстрактного класса `siteclass` из модуля `sitemodule`.
Начнем с подключения модуля:
<? require($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
\Bitrix\Main\Loader::includeModule('sitemodule');
Далее - определяем сущность класса и выбираем ИД хайлоад-инфоблока со справочником:
$mainEntity = new siteclass();
$entityID = $mainEntity::ATC; // ATC - константа с ИД хайлоад-инфоблока
и выбираем все значения из справочника:
$tmp = $mainEntity->GetAllInVacabulary($entityID);
$arRows = array();
foreach ($tmp['ITEMS'] as $arRow) {
$arRows[] = $arRow;
}
Метод, выбирающий все значения из справочника имеет вид:
function GetAllInVacabulary($entity){
return self::SearchInHLVacabulary('', $entity);
}
function SearchInHLVacabulary($code, $VHlblock, $groupKey = 'UF_XML_ID'){
$code = trim($code);
$cacheParams = array(
'code' => $code,
'entity_id' => $VHlblock,
'module' => 'HighloadIblock',
'function' => __FUNCTION__
);
$cache_id = md5(serialize($cacheParams));
$cache_dir = __CLASS__ . '/' . $cacheParams['module'];
$obCache = new CPHPCache;
if ($obCache->InitCache(36000, $cache_id, $cache_dir)) {
$arResult = $obCache->GetVars();
$arResult['CACHE'] = 'Y';
} elseif (\Bitrix\Main\Loader::includeModule('highloadblock') && $obCache->StartDataCache()) {
global $CACHE_MANAGER;
$CACHE_MANAGER->StartTagCache($cache_dir);
$hlblock_requests = HL\HighloadBlockTable::getById($VHlblock)->fetch();//requests
$entity_requests = HL\HighloadBlockTable::compileEntity($hlblock_requests);
$entity_requests_data_class = $entity_requests->getDataClass();
$main_query_requests = new Entity\Query($entity_requests_data_class);
$main_query_requests->setSelect(array('ID', 'UF_NAME', 'UF_XML_ID'));
if (strlen($code) > 0) {
$main_query_requests->setFilter(array('UF_XML_ID' => $code));
}
$result_requests = $main_query_requests->exec();
$result_requests = new CDBResult($result_requests);
$arResult['ITEMS'] = array();
while ($row_requests = $result_requests->Fetch()) {
$arResult['ITEMS'][$row_requests[$groupKey]] = $row_requests;
$CACHE_MANAGER->RegisterTag('hl' . $cacheParams['entity_id'] . $row_requests[$groupKey]);
}
$CACHE_MANAGER->RegisterTag('hl' . $cacheParams['entity_id']);
$CACHE_MANAGER->EndTagCache();
$obCache->EndDataCache($arResult);
} else {
$arResult['ITEMS'] = array();
}
return $arResult;
}
Далее пишем пошаговый аяксовый js-обработчик/ Контейнер для информации о ходе обработки:
<div id="loading"><p class="persents">
Обработка: <span class="pv">0</span>% (Строка: <span class="rv">1</span>/<?= count($arRows) ?>)
</p>
</div>
И сам js-скрипт (простите за смесь jquery и битриксовой JS-библиотеки - я ленивый и очень не хочется имеющийся в битриксе прелоадер переписывать):
<script type="text/javascript">
var ajaxurl = '/bitrix/admin/sitemodule_ajax.php'; // url to pass ajax requests
var Rows = <?=json_encode($arRows);?>;
var countRows = <?=count($arRows);?>;
function work_with_row(num, callback, triesCount) {
var Persents = (parseInt(num) + 1) * 100 / parseInt(countRows);
$('p.persents').find('span.pv').html(Math.round(Persents));
$('p.persents').find('span.rv').html(parseInt(num) + 1);
$.ajax({
type: "POST",
url: ajaxurl,
data: {action: 'ClearVacabulary', Row: Rows[num],entity:<?=$entityID?>},
dataType: "json",
async: false,
success: function () {
callback(num + 1);
},
error: function () {
triesCount = triesCount || 0;
if (triesCount > 5) {
callback(num + 1);
} else {
setTimeout(function () {
work_with_row(num, callback, triesCount + 1);
}, 5000);
}
}
});
}
$(document).ready(function () {
var wait = BX.showWait('loading');
var maxIndex = Rows.length;
var run = function (index) {
if (index < maxIndex) {
setTimeout(function () {
work_with_row(index, run);
}, index > 0 ? 200 : 0)
} else {
$('p.persents').html('Обработка завершена');
BX.closeWait('loading', wait);
}
};
run(0);
});
</script>
немного по коду: Сначала определяем путь к файлу-обработчику аяксовых-запросов.
Далее - определяем переменные с набором полученных значений справочника и с количеством этих значений.
Функция work_with_row обрабатывает одно конкретное значение справочника. Подробнее о функции - ниже.
Работа скрипта начинается с показа битриксового прелоадера и определения значения, когда обработчику пора остановиться.
Дальше - вызываем функцию-обработчик шага, которая с таймаутом в 200 милисекунд запускает обработку выбранного шага с рекурсией.
Когда все строки обработаны - показываем пользователю соответствующее сообщение.
Теперь по функции обработки каждой строки. Сначала функция меняет индикатор работы скрипта. После этого, идет отправка аякс-запроса на сервер. В случае успешно отработавшего запроса, запускается обработка следующего шага. Если же обработка аякс-запроса не отработала (сервер, например свалился, или интернет-соединение пропало), делаем еще 5 попыток с увеличенным таймаутом (5 сек.). Если за 5 попыток так и не удалось произвести запрос - пропускаем его.
Последний штрих - обработчик аякс-запроса. В принимающем файле прописываем:
\Bitrix\Main\Loader::includeModule('sitemodule');
$mainEntity = new siteclass();
$arRow = $_POST['Row'];
$entityID = $_POST['entity'];
if(!empty($arRow) && intval($arRow['ID'])>0 && intval($entityID)>0){
$tmp=$mainEntity->SearchInHLVacabulary($arRow['UF_XML_ID'],$entityID,'ID');
if(count($tmp['ITEMS'])>1){
$isFirst = true;
$arResult['ITEMS'] = $tmp['ITEMS'];
foreach ($tmp['ITEMS'] as $arSubRow) {
if($isFirst){
$isFirst = false;
continue;
}
$arResult['RES'] = $mainEntity->RemoveFromHlVacabulary(intval($arSubRow['ID']),$entityID);
}
}
}
echo json_encode($arResult);
Снова подключаем класс, определяем сущность, получаем инфомрацию о строке. Далее - ищем информацию в справочнике по значению XML_ID - результатом будет
список всех значений справочника с одинаковым внешним кодом. Далее - запускаем обработку по полученным значениям (если значений больше 1).
Первое значение пропускаем, а все остальные - удаляем. Метод SearchInHLVacabulary имеет вид:
function RemoveFromHlVacabulary($arValueID, $VHlblock){
if (!\Bitrix\Main\Loader::includeModule('highloadblock')) return false;
if(intval($arValueID)<=0) return false;
global $CACHE_MANAGER;
$rsData = HL\HighloadBlockTable::getList(array('filter' => array('ID' => $VHlblock)));
if (!($arData = $rsData->fetch())) {
return 'Инфоблок не найден';
}
$Entity = HL\HighloadBlockTable::compileEntity($arData);
$DataClass = $Entity->getDataClass();
$result = $DataClass::delete($arValueID);
if (!$result->isSuccess())
return array('error' => $result->getErrorMessages());
else {
$CACHE_MANAGER->ClearByTag('hl' . $VHlblock . $arValueID);
$CACHE_MANAGER->ClearByTag('hl' . $VHlblock);
return 'OK!';
}
}
Вот такой вот маленький пример:) Понимаю, что для удаления можно было бы и более оптимальный код написать, с меньшим количеством отправок данных на сервер, но на примере этого кода можно делать не только удаление, но и более тяжелые обработки данных
