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

Как правило, при запуске интернет-магазина приходится сталкиваться с проблемой реструктуризации каталога. У многих каталог уже есть в том или ином виде - в 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.
Страницы: 1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  
0
Валера
29.08.2016 00:00:36
А как быть с выводом каталога в публичке? Прямо так и выводите "с решетками" - или их режете на этапе вывода? Не проще ли было создавать разделы с "внешними кодами" определенного формата, по которым и определять - "хэштэг" это или нет?
0
29.08.2016 18:37:02
Валерий, при выводе разделов каталога в публичке - "решетки" режем result_modifier-ами. Мы намеренно использовали префикс (#) перед названием раздела - чтобы можно было определять, обычный ли это раздел или "хэштэг", внешние коды разделов для этой цели не подходили, т.к. они не везде передаются в шаблоны компонентов - например, в "хлебных крошках" их нет.
0
16.01.2025 18:20:16
-1 OR 2+520-520-1=0+0+0+1 --
0
16.01.2025 18:20:16
-1 OR 3+520-520-1=0+0+0+1 --
0
16.01.2025 18:20:16
-1 OR 2+80-80-1=0+0+0+1
0
16.01.2025 18:20:16
-1 OR 3+80-80-1=0+0+0+1
0
16.01.2025 18:20:16
-1' OR 2+610-610-1=0+0+0+1 --
0
16.01.2025 18:20:17
-1' OR 3+610-610-1=0+0+0+1 --
0
16.01.2025 18:20:17
-1' OR 2+634-634-1=0+0+0+1 or 'fcYRaVwB'='
0
16.01.2025 18:20:17
-1' OR 3+634-634-1=0+0+0+1 or 'fcYRaVwB'='
0
16.01.2025 18:20:17
-1" OR 2+926-926-1=0+0+0+1 --
0
16.01.2025 18:20:17
-1" OR 3+926-926-1=0+0+0+1 --
0
16.01.2025 18:20:21
1*if(now()=sysdate(),sleep(15),0)
0
16.01.2025 18:20:26
10'XOR(1*if(now()=sysdate(),sleep(15),0))XOR'Z
0
16.01.2025 18:20:30
10"XOR(1*if(now()=sysdate(),sleep(15),0))XOR"Z
0
16.01.2025 18:20:34
(sel ect(0)from(select(sleep(15)))v)/*'+(select(0)fr om(sel ect(sleep(15)))v)+'"+(select(0)fr om(select(sleep(15)))v)+"*/
0
16.01.2025 18:20:39
1-1; waitfor delay '0:0:15' --
0
16.01.2025 18:20:44
1-1); waitfor delay '0:0:15' --
0
16.01.2025 18:20:49
1-1 waitfor delay '0:0:15' --
0
16.01.2025 18:20:55
1yU4Y73aM'; waitfor delay '0:0:15' --
0
16.01.2025 18:21:01
1-1 OR 938=(SEL ECT 938 FR OM PG_SLEEP(15))--
0
16.01.2025 18:21:06
1-1) OR 923=(SEL ECT 923 FR OM PG_SLEEP(15))--
0
16.01.2025 18:21:13
1-1)) OR 274=(SEL ECT 274 FR OM PG_SLEEP(15))--
0
16.01.2025 18:21:19
1eiMGV6Gf' OR 866=(SEL ECT 866 FR OM PG_SLEEP(15))--
0
16.01.2025 18:21:27
1BN7ppLt8') OR 230=(SEL ECT 230 FR OM PG_SLEEP(15))--
0
16.01.2025 18:21:33
1VhJ5CHLu')) OR 318=(SEL ECT 318 FR OM PG_SLEEP(15))--
0
16.01.2025 18:21:38
1*DBMS_PIPE.RECEIVE_MESSAGE(CHR(99)||CHR(99)||CHR(99),15)
0
16.01.2025 18:21:42
1'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'
0
16.01.2025 18:21:43
0
16.01.2025 18:40:47
-1 OR 2+968-968-1=0+0+0+1 --
0
16.01.2025 18:40:47
-1 OR 3+968-968-1=0+0+0+1 --
0
16.01.2025 18:40:47
-1 OR 2+856-856-1=0+0+0+1
0
16.01.2025 18:40:48
-1 OR 3+856-856-1=0+0+0+1
0
16.01.2025 18:40:48
-1' OR 2+589-589-1=0+0+0+1 --
0
16.01.2025 18:40:48
-1' OR 3+589-589-1=0+0+0+1 --
0
16.01.2025 18:40:48
-1' OR 2+285-285-1=0+0+0+1 or 'ZncZ6UJM'='
0
16.01.2025 18:40:48
-1' OR 3+285-285-1=0+0+0+1 or 'ZncZ6UJM'='
0
16.01.2025 18:40:48
-1" OR 2+750-750-1=0+0+0+1 --
0
16.01.2025 18:40:48
-1" OR 3+750-750-1=0+0+0+1 --
0
16.01.2025 18:40:52
1*if(now()=sysdate(),sleep(15),0)
0
16.01.2025 18:40:56
10'XOR(1*if(now()=sysdate(),sleep(15),0))XOR'Z
0
16.01.2025 18:40:59
10"XOR(1*if(now()=sysdate(),sleep(15),0))XOR"Z
0
16.01.2025 18:41:04
(sel ect(0)from(select(sleep(15)))v)/*'+(select(0)fr om(sel ect(sleep(15)))v)+'"+(select(0)fr om(select(sleep(15)))v)+"*/
0
16.01.2025 18:41:13
1-1; waitfor delay '0:0:15' --
0
16.01.2025 18:41:17
1-1); waitfor delay '0:0:15' --
0
16.01.2025 18:41:21
1-1 waitfor delay '0:0:15' --
0
16.01.2025 18:41:25
1atYaMJKg'; waitfor delay '0:0:15' --
0
16.01.2025 18:41:31
1-1 OR 711=(SEL ECT 711 FR OM PG_SLEEP(15))--
0
16.01.2025 18:41:37
1-1) OR 251=(SEL ECT 251 FR OM PG_SLEEP(15))--
0
16.01.2025 18:41:42
1-1)) OR 201=(SEL ECT 201 FR OM PG_SLEEP(15))--
0
16.01.2025 18:41:46
1rrVk8Q6a' OR 595=(SEL ECT 595 FR OM PG_SLEEP(15))--
0
16.01.2025 18:41:50
1z04wyof0') OR 813=(SEL ECT 813 FR OM PG_SLEEP(15))--
0
16.01.2025 18:41:55
1mcevRu6V')) OR 614=(SEL ECT 614 FR OM PG_SLEEP(15))--
0
16.01.2025 18:41:59
1*DBMS_PIPE.RECEIVE_MESSAGE(CHR(99)||CHR(99)||CHR(99),15)
0
16.01.2025 18:42:03
1'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'
0
16.01.2025 18:42:04
1
16.01.2025 18:15:06
1
16.01.2025 18:15:27
1
16.01.2025 18:15:45
1
16.01.2025 18:15:48
1
16.01.2025 18:15:52
1
16.01.2025 18:15:55
1
16.01.2025 18:15:58
1
16.01.2025 18:16:01
1
16.01.2025 18:16:05
1
16.01.2025 18:16:08
1
16.01.2025 18:16:12
1
16.01.2025 18:16:19
1
16.01.2025 18:16:22
1
16.01.2025 18:16:25
1
16.01.2025 18:16:27
1
16.01.2025 18:16:31
1
16.01.2025 18:16:33
1
16.01.2025 18:16:37
1
16.01.2025 18:16:40
1
16.01.2025 18:16:43
0
16.01.2025 18:16:47
0
16.01.2025 18:16:49
0
16.01.2025 18:16:50
0
16.01.2025 18:16:52
0
16.01.2025 18:16:54
0
16.01.2025 18:16:55
0
16.01.2025 18:16:58
0
16.01.2025 18:16:59
0
16.01.2025 18:17:01
0
16.01.2025 18:17:03
0
16.01.2025 18:17:05
0
16.01.2025 18:17:06
0
16.01.2025 18:17:08
0
16.01.2025 18:17:09
0
16.01.2025 18:17:11
0
16.01.2025 18:17:13
0
16.01.2025 18:17:15