понедельник, 20 мая 2013 г.

Всегда актуальная, гибкая и настраиваемая sitemap.xml для Битрикс

К сожалению, мое предыдущее решение sitemap для Битрикс оказалось нежизнеспособным. Но не бывает худа без добра: я получила очень много откликов как в сообществе битрикс разработчиков, так и на емайл. Удивительно, скольким людям интересна эта тема. Многие присланные мне идеи помогли мне по-новому взглянуть на проблему генерации sitemap для Битрикс. Я поняла, что большинству пользователей нужна просто sitemap, генерируемая по расписанию, но не привязанная к модулю поиска.

К примеру, есть интернет-магазин. У него есть инфоблок с каталогом и есть инфоблок с SEO-статьями для поисковика. Естественно, целесообразно, чтобы SEO-статьи индексировались поисковиками в первую очередь, а вот во внутреннем поиске по сайту, они как раз таки не нужны.

Я подумала и написала новое решение, позволяющее администраторам сайта настроить генерацию sitemap быстро, просто и опционально. В маркетплейсе оно появится на днях, когда  мне отрисуют для него иконку, а я пока его анонсирую (и соберу новые пожелания).
На этот раз я решила добавить генератор sitemap в админку как новый тип экспорта.

Можно настроить, какие инфоблоки выгружать в сайтмеп и с каким приоритетом индексирования (я имею в виду пути к элементам и разделам инфоблока, взятые из его настроек).

Можно настроить, какие статичные каталоги и страницы (на основе меню сайта) выгружать в сайтмеп и с каким приоритетом.

Нажимаем «Сформировать» - и sitemap формируется.



Можно добавить профили экспорта sitemap таким же образом, как для стандартных видов экспорта.




А профиль в свою очередь можно привязать к крону.


И конечно, проверим в валидаторе, что получилось:

Да, Яндекс ее полюбит.

понедельник, 13 мая 2013 г.

Интеграция 1С Предприятия и 1С Битрикс: про то, как мы добавили возможность передачи архива по FTP


Работая над одним из проектов, я столкнулась с тем, что когда архив с данными от 1С больше 1,5Г - обмен проходит нестабильно - рвался именно на этапе передачи файла (иногда рвался, а иногда - нет). Возможно, это общая проблема, а возможно – на конкретном сервере, а сервер там солидный. Заказчик же хотел иметь возможность делать полную выгрузку каталога, полная выгрузка представляла собой архив, объемом около 6Г.

Как известно, на третьем шаге стандартного протокола обмена 1С Предприятие по частям посылает Битриксу архив с файлами обмена в виде POST (я рассматриваю наш частный случай – объемный архив).

Я предложила 1С Программисту переписать механизм импорта каталога товаров на его и на моей стороне так, чтобы на 3м шаге обмена 1С Предприятие выкладывало архив на сервер по фтп и просто сообщало сайту об этом. Посовещавшись, решили не заменять способ передачи архива от 1С на сайт, а сделать возможность передавать его как стандартно через http в виде POST, так и посредством ftp. Наш видоизмененный протокол обмена между 1С Предприятием и 1С Битрикс выглядит следующим образом:

Шаг 1 и 2 – такие же, как в стандартном протоколе обмена
Шаг 3: 

Вариант 1:
http://<сайт>/bitrix/admin/1c_exchange.php?type=catalog&mode=file&filename=<имя файла>&method=http
1С посылает запрос и загружает на сервер файлы обмена в формате CommerceML 2, посылая содержимое файла или его части в виде POST. 

Вариант 2:
1C заливает сайт по ftp, а затем посылает сайту запрос:
http://<сайт>/bitrix/admin/1c_exchange.php?type=catalog&mode=file&filename=<имя файла>&method=ftp
(тут нужно учесть возможность того, что если 1С не успеет залить архив на сервер за время жизни сессии, то Битрикс уже может разовтаризовать 1С, и тогда перед этим запросом нужно снова авторизоваться, но у нас за время жизни сессии все по ftp залиться успевало, и не стали пока эту возможность учитывать)

В случае успешной записи файла 1С-Битрикс выдает "success".

Шаг 4 – такой же, как в стандартном протоколе.

Таким образом, мы видоизменили только 3й шаг протокола обмена – остальные шаги остались стандартными.

Для того, чтобы реализовать эти изменения на стороне сайта, я сделала в ранее уже кастомизированном компоненте импорта (но конкретно на этом участке его код совпадал со стандартным компонентом) следующее:

В блоке 

if(($_GET["mode"] == "file") && $ABS_FILE_NAME) {}

Сразу после коммента //And save it the file

Заменила условие 

if($DATA_LEN > 0) 
на 
if($DATA_LEN > 0 || $_GET["method"]='ftp')
И все – после этого компонент успешно принимал архив как загруженный кусочками в POST, так и залитый целиком на ftp (при условии, что 1С присылала соответствующий маркер). Возможно, что при этом не все возможные ошибки вылавливаются, но тогда в любом случае возникает ошибка при попытке скрипта распаковать архив, поэтому углубляться не стала.

На тестирование же опять ушло много-много часов. Я уже привыкла, что при кастомизации интеграции основное время нужно закладывать не на написание кода, а на тестирование. Дополнили лог обмена на стороне сайта фиксацией времени прихода запросов от 1С – это помогло следить за синхронностью. Важно, чтобы 1С выжидала, пока весь файл загрузится на ftp, прежде чем посылать сайту очередной запрос.

В итоге проблема заливки выгрузки объемом более 1,5Г была решена.

вторник, 7 мая 2013 г.

Рекурсивный алгоритм обхода дерева (General tree) (в контексте разделов инфоблока Битрикс)

Во время моей учебы в университете СИАОД казался мне скучнейшим предметом. Мы лежали на партах и не знали, куда себя девать, считая секунды до конца. Спустя годы я понимаю, что должна быть благодарна нашему преподавателю, который не позволял нам пропускать свои заунылые лекции. Потому что, занимаясь оптимизацией web-проектов на битрикс, я сплошь и рядом вижу ситуации, когда незнание такого простого алгоритма, как обход естественного дерева, вынуждает разработчиков делать лишние запросы к базе данных.

Дерево в своем общем виде - это направленный граф, каждый узел которого может содержать неограниченное число потомков. Разделы инфоблока в Битрикс с их неограниченной вложенностью по факту представляют собой дерево.

В исходниках типового древовидного меню Битрикс мы можем видеть один из алгоритмов обхода дерева, но он мне, честно говоря, не очень нравится - он не достаточно нагляден и не очень гибок. Мои стажеры испытывают трудности, когда им нужно видоизменить этот алгоритм. Поэтому я хочу описать другой - самый простой, на мой взгляд.

Сначала я выбираю все активные разделы из инфоблока

    $map=array();
    if(isset($arParams["IBLOCK_MAP_ID"]))
    {   
        $arFilter = Array('IBLOCK_ID'=>$arParams["IBLOCK_MAP_ID"], 'GLOBAL_ACTIVE'=>'Y');
        $db_list = CIBlockSection::GetList(Array("left_margin"=>"asc"), $arFilter, true, array("UF_ID_IB"));
       
        while($ar_result = $db_list->GetNext())
        {
            $map[''.$ar_result['ID']]=$ar_result;
         }
   
    }

Многие думают, что функция CIBlockSection::GetTreeList или CIBlockSection::GetList с сортировкой "left_margin"=>"asc" сразу возвращает нам дерево. Но она возвращает не дерево, а один из вариантов его обхода. Хорошо, если этот обход совпадает с тем путем, по которому вам нужно обойти дерево, но часто нужно обойти его в другом порядке. В массиве же $map каждый потомок содержит ссылку на предка, но предок не содержит ссылок на потомков.

В массиве $map сейчас потомки идут сразу за своими предками - то есть прямой ссылки нет - она определяется порядком следования элементов в массиве. Да, можно обойти дерево как угодно и в этом его представлении - примеров в исходниках Битрикс - масса. Но это, как я уже говорила - громоздкие алгоритмы. Если нам нужно, к примеру, вывести ветку, начиная с определенного узла, нам придется перебирать весь массив в цикле, пока мы не дойдем до этого узла (а чаще делают дополнительный аякс запрос по мере необходимости - в лучшем случае, а в худшем - запросы в цикле фигачат). А я предпочитаю не делать даже аякс запросов, если можно их не делать. Поэтому я преобразую дерево к другому очень удобному для работы виду - гроздьями.

$map_sec=array();
    foreach($map as $key=>$val){
   
        if($val['IBLOCK_SECTION_ID']>0){
            $parent_id=$map[$val['IBLOCK_SECTION_ID']]['ID'];
            $map_sec[''.$parent_id]['CODE']=$map[$parent_id]['CODE'];
            $map_sec[''.$parent_id]['NAME']=$map[$parent_id]['NAME'];
            $map_sec[''.$parent_id]['UF_ID_IB']=$map[$parent_id]['UF_ID_IB'];
            $map_sec[''.$parent_id]['CHILDS'][]=array("ID"=>$val['ID'],"CODE"=>$val['CODE'],"NAME"=>$val['NAME'],"UF_ID_IB"=>$val['UF_ID_IB']);
           
        }

    }
   
 $arResult['MAP']=$map_sec;

В массиве  $arResult['MAP'] у нас дерево в виде гроздей - каждый предок содержит ссылки на своих потомков. Вот к нему мы уже можем применять классические алгоритмы работы с деревьями в их неизменном виде.

Функция рекурсивного обхода такого дерева будет выглядеть следующим образом:

function outTree($category_arr,$parent_id) {
       
        if (isset($category_arr[$parent_id])) {
           
                foreach ($category_arr[$parent_id]['CHILDS'] as $value) { //Обходим
                    echo "<div style=\"margin-left:" . ($value['DEPTH_LEVEL']  * 25) . "px;\">" . $value["NAME"] . "</div>";
                  
                    if (count($category_arr[$value["ID"]]['CHILDS'])>0){
                        //Рекурсивно вызываем эту же функцию, но с новым $parent_id
                        outTree($category_arr,$value["ID"]);
                    }
                    else{
                         //Выводим листик
                     }
                 
                }
           
           }
       
    }


Теперь для того, чтобы вывести ветку дерева, начиная с любого узла $id_uzla достаточно написать:
outTree($arResult['MAP'],$id_uzla);
то есть мы можем выводить ветки дерева в любом порядке, в каком они нужны нам в шаблоне, например, чтобы вывести иерархию каталога на вложенных друг в друга закладочках, не обращаясь при этом к базе данных каждый раз при переключении закладки.

В практическом плане. Данный метод (с несколькими модификациями, естественно) я использовала для представления иерархии каталога товаров в виде вот таких вложенных закладочек. И вложенность там ничем не ограничена (только фантазией дизайнера).



Обращение к базе - только единожды (одна функция CIBlockSection::GetList) - и все построено на результатах ее работы. Работающий пример тоже покажу - ближе к запуску проекта, для которого писала это.

среда, 1 мая 2013 г.

Дикие ягоды и подводные камни... (опыт интеграции 1С Битрикс и 1С ПРедприятия)

"Хочу все, как на дикой ягоде (как на wildberries.ru)" - над таким незамысловатым заданием заказчика нам недавно довелось поработать (нам - это в данном случае мне и моей помощнице Юлии Ханусяк). Уточнив, что именно заказчик хочет "как на дикой ягоде", я поняла, что он хочет такую же организацию каталога и карточки товаров, как там и чтобы каталог наполнялся на стороне 1С и уже не требовал дозаполнения на стороне сайта. Несколько разработчиков до меня благоразумно отказались от этой задачи, я же изначально не увидела в ней всех подводных камней. Мы определились, какие задачи заказчик хочет видеть решенными до запуска магазина, а какие мы оставим на будущее, и я приступила к реализации.

Тут следует упомянуть об особенностях организации каталога на wildberries.ru.
Каждый товар (предмет одежды) имеет торговые предложения, обладающие характеристиками цвета и размера. И к каждому цвету привязано свое фото, и если пользователь в карточке товара кликает по определенному цвету,  то картинка этого цвета становится на место большой картинки. 

Как известно, подготовить структуру инфоблоков для такой задачи на стороне сайта - не составляет большого труда. Об организации каталога с торговыми предложениями хорошо написано в учебных курсах для разработчиков, и я не буду пересказывать их. Привязать картинки к торговым предложениям в Битрикс тоже вполне возможно.

На стороне 1С (у клиента стандартная 1С УТ 11.0) - тоже, казалось бы, нет проблемы - включаем учет по характеристикам. Создаем характеристики "цвет" и "размер" для нужных номенклатурных групп, правильно заводим документы прихода на склад и документы установки цен, создаем профиль обмена с фильтром по наличию на складе и делаем выгрузку.

И вот тут и всплыл большой и жирный подводный камень: на стороне 1С Предприятия в стандартной конфигурации УТ нет никакой возможности указать, к какому цвету относится та или другая прикрепленная к номенклатурной позиции картинка. 

1С программиста у заказчика не было, и заказчик искренне полагал, что, взявшись делать интеграцию, я должна делать ее с обеих сторон. "Что же, Юля, в следующий раз ты будешь тщательнее продумывать текст договора" - сказала я себе и вспомнила о замечательном 1С Франчайзи, интеграторе и внедренце, с которым я имею честь быть знакомой, а именно об Анатолии Полякове (Skype: tolpolyakov). К нему я и обратилась за консультацией по указанной проблеме. Анатолий проконсультировал меня и, кстати, отказался от денег, попросив взамен дружбу и аналогичные дружеские консультации по Битриксу, если они понадобятся. Анатолий, я не забываю добро и помню о своих обещаниях. Но я отвлеклась от темы. 

Анатолий предложил мне следующий способ решения задачи привязки конкретной картинки к конкретному цвету: на стороне 1С отказаться от учета по характеристики "цвет", вести учет только по характеристике "размер", на стороне 1С считать, что разные цвета одного товара - это разные товары, но иметь некое объединяющее поле и на стороне сайта, кастомизируя импорт, сливать несколько товаров, различающихся цветом, в один товар с торговыми предложениями, обладающие цветом и размером. 

Этот способ мне не очень понравился... и не только из-за его трудоемкости. Мне нравится сама идея ведения учета товара в 1С Предприятии по характеристикам, и отказ от полноценного использования такого учета ради реализации маленькой фичи показался мне неадекватно большой потерей, хотя заказчик был готов на нее пойти. 

Я думала, думала, думала, и решила, что правильным вариантом будет добавить на стороне 1С свойства к сущности файла, позволяющего назначать файлу привязку к конкретному элементу из справочника цветов, а так же свойства, отвечающего за то, является ли фото - главным видом или видом сзади, и еще одного свойства, отвечающего за то, является ли картинка картинкой по-умолчанию. Я была готова реализовать это на стороне 1С самостоятельно, освежив навыки 6-ти летней давности, но, поговорив с заказчиком, мы решили остановиться на менее изящном, но более простом варианте.

Мы решили использовать в 1С стандартное поле "Описание файла" для хранения нужных нам признаков картинки - цвета и того, является ли она "мордочкой" или "спинкой". 

Договорились, что в описании файла заказчик будет писать, к примеру, "красный№1" если это главная картинка красного цвета, "красный" - если это картинка красного цвета, но не главная, "красный№0" - если это картинка красного цвета, к-я должна быть использована как картинка по-умолчанию, если для какого-то цвета нет картинки.

При импорте в файле обмена описания файлов картинок выгружаются 1С в составе реквизитов товара.

О том, как их можно обработать на стороне сайта - я тоже умолчу в данном посте - я уже опубликовала несколько постов с описанием того, как я кастомизирую импорт. А то, что у нас получилось - можно увидеть на сайте luckyfamily.ru 

Конечно, пройдет еще не мало итераций разработки, пока этот проект приблизится к своему идеалу - "диким ягодам". Но каталог успешно наполняется на стороне 1С, регулярно импортируется на сайт и мы знаем, какая фотка - какого цвета, на какой мордочка, а на какой спинка.