Мультикаталог на "Битриксе"

Как правило, при запуске интернет-магазина приходится сталкиваться с проблемой реструктуризации каталога. У многих каталог уже есть в том или ином виде - в 1С или в другой учетной системе, но его вид оставляет желать лучшего: он хорошо подходит для менеджеров, продающих в оффлайне, но совершенно не годится для публикации на сайте. В этой статье я расскажу об интересном подходе, который мы применили при разработке структуры каталога для интернет-магазина издательства "Росмэн".
[spoiler]
Постановка задачи

В издательстве используется "самописная" учетная система, в которой товары (в основном, книги) не имеют разделов-родителей, а обладают набором свойств - "жанр", "серия", "возраст", "бренд" и т.п.; при этом каждое значение свойства фактически является родителем для тех или иных товаров, например:
Книга "Огниво": жанр - "Художественная литература для детей", автор - "Андерсен", серия - "В гостях у сказки"
Книга "Лунтик. Водная раскраска": тип - "Раскраска", жанр - "Подготовка к школе", бренд - "Лунтик"
Необходимо было создать удобную для пользователя структуру каталога на сайте (с минимальными затратами по ручной обработке) и реализовать регулярный автоматический импорт каталога.

Идея

Как известно, в архитектуре "Битрикса" все рассчитано на то, что есть древовидная структура каталога (инфоблока) - разделы-родители и "вложенные" в них элементы (товары). Свойства в "Битриксе" не могут быть родителями элементов инфоблока. Но почему бы использовать значения свойств - в качестве названий разделов каталога? На основе этой идеи мы составили схему регулярного импорта данных из учетной системы издательства - в "Битрикс". Для того, чтобы отличать "ветки" каталога, созданные на основе свойств, мы использовали "хэштэги" наименований свойств - в качестве названий разделов. Часть схемы приведена ниже: красным - разделы 1-го уровня, оранжевым - разделы 2-го уровня ("хэштэги", созданные на основе свойств), желтым - разделы 3-го уровня, зеленым - товары.
bb45812b62a08fb92ca4dfa736da81ac.png

Реализация

Поскольку каталог издательства создается и обновляется собственным скриптом импорта, мы добавили функции, которые создают и обновляют структуру разделов каталога на основе значений свойств товаров и привязывают товары к соответствующим разделам. Полные коды всех функций приводить здесь не буду, основных моментов два, (1) - привязка товаров c созданием новых разделов внутри "хэштэгов" и (2) - обновление структуры разделов:

Привязка товаров и создание новых разделов внутри "хэштэгов":
function SetElementHashSections($arElement,$arProperties,$arBrand) {
   $arHashSectionsResult = array('RESULT'=>true,'MESSAGE'=>'');
   if($arElement['IBLOCK_SECTION_ID']) {
      if(!is_array($arElement['IBLOCK_SECTION_ID']))
         $arElementGroups = $arElement['IBLOCK_SECTION_ID'] = array($arElement['IBLOCK_SECTION_ID']);
      else
         $arElementGroups = $arElement['IBLOCK_SECTION_ID'];
      
      foreach($arElement['IBLOCK_SECTION_ID'] as $iblock_section_id) {
         $nav = CIBlockSection::GetNavChain(false, $iblock_section_id);
         while($ar_nav = $nav->GetNext())
            $sect_path[] = $ar_nav['ID'];
         $rsHashSections = CIBlockSection::GetList(
            array(),
            array(
               "NAME"=>"#%",
               "SECTION_ID"=>$sect_path[0],
               "IBLOCK_ID"=>$arElement['IBLOCK_ID']
            )
         );
         while($arHashSection = $rsHashSections->GetNext()) {
            $arHashName = explode('/',$arHashSection['NAME']);
            $arHashName[0] = substr($arHashName[0],1);
            foreach($arHashName as $depth=>$hashname) {
               $PropCode = $arProperties[$hashname];
               if($PropCode == 'BRAND')
                  $PropValue = $arBrand[$arElement['PROPERTY_VALUES'][$PropCode]];
               else
                  $PropValue = $arElement['PROPERTY_VALUES'][$PropCode];   
               
               if($PropCode && $PropValue) {
                  if(is_array($PropValue))
                     $arPropValue=$PropValue;
                  else
                     $arPropValue=array($PropValue);
                  foreach($arPropValue as $PropValue) {
                     $rsHashSubSection = CIBlockSection::GetList(
                        array(),
                        array(
                           "=NAME" => $PropValue,
                           "SECTION_ID" => ($depth == 0)? $arHashSection['ID']:$subsection_parent_id,
                           "IBLOCK_ID"=>$arElement['IBLOCK_ID']
                        )
                     );
                     if($arHashSubSection = $rsHashSubSection->GetNext()) {
                        $arElementGroups[] = $subsection_id = $arHashSubSection["ID"];
                     }
                     else {
                        $bs = new CIBlockSection;
                        $subsection_id = $bs->Add(array(
                           "ACTIVE" => 'Y',
                           "NAME" => $PropValue,
                           "CODE" => GetSymCode($PropValue),
                           "IBLOCK_SECTION_ID" => ($depth == 0)? $arHashSection['ID']:$subsection_parent_id,
                           "IBLOCK_ID" => $arElement['IBLOCK_ID']
                        )); 
                        if($subsection_id) {
                           $arElementGroups[] = $subsection_id;
                           $arHashSectionsResult = array(
                              'RESULT'  =>   true,
                              'MESSAGE' =>   "Создан подраздел \"".
                                             $PropValue.
                                             "\" внутри хэштэга ".$arHashSection['NAME']."\n"
                           );
                        }
                        else
                           $arHashSectionsResult = array(
                              'RESULT'  =>   false,
                              'MESSAGE' =>   "Создание подраздела \"".
                                             $PropValue.
                                             "\" внутри хэштэга ".$arHashSection['NAME']." не получилось: ".
                                             $bs->LAST_ERROR."\n"
                           );
                     }
                  }
               }
               if($depth == 0)
                  $subsection_parent_id = $subsection_id;
            }
         }
      }
      
      if(count($arElementGroups)>1)
         CIBlockElement::SetElementSection($arElement['ID'], $arElementGroups);
   }
   return $arHashSectionsResult;
}
Обновление структуры разделов - активация/деактивация существующих разделов-хэштэгов:
function MakeHashSubSectionsActive($IBLOCK_ID) {
   $rsHashSections = CIBlockSection::GetList(array(),array("NAME"=>"#%","DEPTH_LEVEL"=>2,"IBLOCK_ID"=>$IBLOCK_ID));
   while($arHashSection = $rsHashSections->GetNext()) {
      $rsHashSubSection = CIBlockSection::GetList(
         array(),
         array(
            "SECTION_ID"=>$arHashSection['ID'],
            "IBLOCK_ID"=>$arElement['IBLOCK_ID']
         ),
         true
      );
      while($arHashSubSection = $rsHashSubSection->GetNext()) {
         if(!$arHashSubSection['ELEMENT_CNT'] && $arHashSubSection['ACTIVE']=='Y') {
            $bs = new CIBlockSection;
            $bs->Update($arHashSubSection['ID'], array("ACTIVE"=>"N"));
            echo "ОК Деактивирован подраздел \"".$arHashSubSection['NAME']."\" хэштэга ".$arHashSection['NAME']."\n";
         }
         elseif($arHashSubSection['ELEMENT_CNT'] && $arHashSubSection['ACTIVE']=='N') {
            $bs = new CIBlockSection;
            $bs->Update($arHashSubSection['ID'], array("ACTIVE"=>"Y"));
            echo "ОК Активирован подраздел \"".$arHashSubSection['NAME']."\" хэштэга ".$arHashSection['NAME']."\n";
         }
      }
   }
}
Отмечу, что аналогичным образом можно реализовать соответствующий подход и для каталогов, обновляемых вручную через админку сайта или выгрузкой из 1С, добавив обработчики событий создания/изменения элемента инфоблока: OnBeforeIBlockElementAdd/OnBeforeIBlockElementUpdate и/или OnAfterIBlockElementAdd/OnAfterIBlockElementUpdate.
0
Валера
29.08.2016 00:00:36
А как быть с выводом каталога в публичке? Прямо так и выводите "с решетками" - или их режете на этапе вывода? Не проще ли было создавать разделы с "внешними кодами" определенного формата, по которым и определять - "хэштэг" это или нет?
0
29.08.2016 18:37:02
Валерий, при выводе разделов каталога в публичке - "решетки" режем result_modifier-ами. Мы намеренно использовали префикс (#) перед названием раздела - чтобы можно было определять, обычный ли это раздел или "хэштэг", внешние коды разделов для этой цели не подходили, т.к. они не везде передаются в шаблоны компонентов - например, в "хлебных крошках" их нет.