Интеграция одних продуктов с другими, особенно если они для этого не предназначены, редко проводится в белых перчатках. Обычно, по крайней мере, по моему опыту, приходится, засучив рукава, лезть внутрь того и другого продукта, изучать его код, писать хаки, утрамбовывать один в другой ногами. Редко стыковку удается выносить в отдельные файлы, еще реже в классы, с шаблонами тоже бывает все тяжко, особенно, если шаблонов не предусмотрено. Обычно такой код намертво замораживается и приносит проблемы при обновлении продуктов, часто делая их невозможными или очень сложными. В статье я хотел бы рассказать о том, как мне пришлось интегрировать в портал на битриксе другие продукты — фотогалерею, форум и чат.
Хотя, для меня в этом плане довольно удивительно, что, например, форумы, которые по своему смыслу скорее будут прилагаться к порталам, не предоставляют средств интеграции или хотя бы не предусматривают потенциальную возможность в своем коде. По-моему, не так уж сложно вынести шаблоны отдельно, конфиги и инициализационный код так, чтобы его можно было легко подключить из других мест. Да и набившие оскомину глобальные переменные — разве сложно сделать под них отдельный namespace или отдельный класс со статическими переменными? Ну если уж совсем без них не обойтись, так можно хотя бы их в угол положить, чтобы не воняли, и периодически доставать, когда необходимы. Однако, логика берет свое — код написан давно, никто не хочет в нем копаться, работает — не лезь, да и бизнес тоже не поймет — на что это вы время тратите? Вы лучше реализуйте новую фичу, а я с нее бабок нарублю. Так оно и живет. Но, довольно, граф, вы увлеклись. К делу.
В один из прекрасных дней я получил задание по новому для себя проекту. Портал был написан довольно давно, поддерживался с тех пор на уровне администрирования, а требовалось вдохнуть в него новую жизнь. Задачи ставил человек, довольно далекий от программирования и IT в целом, а потому они сводились, в основном, на то, чтобы изменить пункты меню, поменять внешний вид и прочее. Правда, все задачи обсуждению почти не подлежали и должны были быть выполнены любой ценой. Меня запросто могли совершенно искренне спросить: «А почему нельзя искать сразу и на портале, и на форуме? Там же вон есть форма поиска, что же она не везде ищет? А как бы нам сделать, чтобы она везде искала?». И такое невинное, с их точки зрения, задание пришлось бы реализовывать. Посмотрел я на объем работ и пошел знакомиться проектом.
Увидел портал на битриксе. Обычный такой информационный портал, с невеликим функционалом, довольно старый. К порталу как отдельные продукты прилагались фотогалерея, форум и чат, которые по оформлению издалека напоминали портал, но не были с ним никак интегрированы. По всем элементам портала, кроме прочего, стояла одинаковая задача — привести их к одному внешнему виду, а именно — добавить стандартные шапку и подвал от битрикса. Если бы мы жили в эпоху благоденствия, где все люди были бы равны, колбаса бы стоила 2р.20коп., а все программные продукты были бы написаны с учетом последних веяний ООП, задача бы превращалась в легкую познавательную прогулку по шаблонам этих продуктов и десятиминутное написание унаследованного класса или подключение шаблонов. Но тогда было бы скучно жить. Как обычно, битрикс развлек меня по полной.
Итак, сначала небольшая матчасть для тех, кто по каким-то счастливым причинам еще не успел познакомиться с битриксом изнутри. Логика и представление в нем разнесены только в компонентах, ядро же в целом сохранило свою структуру с более ранних версий, а потому там все вперемешку.
Типовая страница на битриксе выглядит так:
При этом в шапке и подвале страницы идет логика битрикса, в том числе и рендер шаблонов. Для того, чтобы можно было в пользовательском скрипте писать логику, которая будет управлять отображением в шаблоне, битрикс использует так называемые функции отложенного вызова. О них уже неоднократно писалось (например, здесь или в документации битрикса), а потому не буду лишний раз распространяться. Для работы таких функций требуется буферизация, а потому в вызове шапки открывается буфер для поглощения содержимого, а в подвале буфер закрывается, и его содержимое вываливается в поток.
Битрикс в своем роде уникален, и, когда хочешь понять, как работает какая-нибудь его часть, обычно лучше сразу лезть в код. Если сразу не стошнит, там можно подчас можно обнаружить очень интересные вещи. Документация — это хорошо, но не всегда там можно найти то, что требуется.А так как для интеграции с другими продуктами битрикс не предназначен (я не имею в виду 1С), то допиливать это придется руками. Итак, надели противогаз и ныряем.
Заплываем недалеко — в файл /bitrix/modules/main/include/prolog_before.php. И видим там блок кода, который отвечает за буферизацию:
<Оффтоп>
Кстати, в 7 версии можно после этого еще увидеть такую строку:
За что люблю битрикс, так за то, что не дает расслабиться и все время держит в тонусе.
</Оффтоп>
И возникает интересный вопрос. Оказывается, есть некоторая настройка buffer_content, которая сигнализирует о том, нужно ли буферизовать содержимое. Примечательно в ней то, что она не настраивается через админку, да и в стандартной нулевой базе (если установить чистый битрикс) отсутствует. Поэтому берется значение по умолчанию, в данном случае, «Y». А что же будет, если ее добавить и установить в «N»? Пробираемся в базу, в таблицу b_option и заводим ее там. Потом чистим папку /bitrix/managed_cache и пробуем писать скрипты. Все, больше содержимое не буферизуется.Правда, теряется возможность менять из страницы внешний вид шапки, например, title. Но тут приходится выбирать, что важнее.
Установить настройку можно и методами самого битрикса, а именно методом COption::SetOptionString. Однако, будьте осторожны, работает он не так, как можно предположить. Мне казалось логичным поведение, если настройки с его помощью устанавливаются на время выполнения скрипта аналогично функции ini_set. Однако этот метод как раз-таки устанавливает настройку прямо в базе. Представляете, так бы работал ini_set — включил там error_reporting(E_ALL) для своего скрипта — и весь хостинг лег. Итак, научились включать/отключать буферизацию, пошли дальше.
Первый клиент на очереди — галерея.
Здесь случай довольно несложный — достаточно просто подключить шапку. Правда, из-за пристрастия к глобальным переменным со стороны обоих продуктов, здесь случился любовный треугольник: битрикс, фотогалерея и глобальная переменная $USER. А потому пришлось убирать ее unset-ом. Ситуация, правда, довольно типичная при интеграции продуктов, которые почему-то упорно отказываются использовать ООП и инкапсуляцию. Ну, да бог их простит. С подвалом все тоже довольно просто — достаточно только вытянуть глобальные переменные (здесь понадобился только $APPLICATION) через global $varname, а то битрикс обидится и упадет: он-то думал, что раз в шапке такую переменную объявил, то и в подвале ее найдет, ан нет.
Второй случай более запущенный — форум vbullentin. Примечателен он тем, что, во-первых, платный, а потому в код с автогеном не полезешь, а во-вторых, тем, что его писали тоже суровые мужики с бородами (кто захочет прочувствовать всю их мощь, можете попробовать разобраться в скрипте поиска по форуму).
В шаблон прописать подключение скрипта не вышло, ибо шаблонизатор тут свой и php-код он не выполняет. Единственное, что он может — это заменить одну php-переменную другой. Пришлось пользоваться этим, раз другого нет. Для заполнения таких переменных форум позволяет писать хаки (hooks) на php, а затем исполнять их по определенным событиям (eval-ом, естественно). Одно из событий вполне подходило, так что вполне было можно цеплять шапку туда. Проблема заключалась в том, что хак исполнялся до вывода шаблона, а потому надо было буферизовать вывод битриксовской шапки. То есть, клин клином вышибают. Я сначала долго пробовал по-хорошему, честно пытался закрыть открытый битриксом буфер, но все мои попытки оказались тщетными — тут мне не удалось его перебороть. Если у кого-то получится, выложите, пожалуйста — мне интересно посмотреть, как оно укрощается.
Поэтому я решил пойти на кардинальные меры и отключить буферизацию с помощью параметра buffer_content способом, который описал выше, если идет рендер форума. То есть в скрипт /bitrix/php_interface/init.php пришлось добавить такой код:
Конечно, криво и неприятно, но зато работает. Теперь можно было просто буферизовать и шапку, и подвал до рендера шаблонов самого форума. А именно:
Аналогично и с подвалом. А затем вывести переменную $bitrixHeader в шаблон. Конечно, и здесь не обошлось без unset-а глобальных переменных, но это уже мелочи.
Ну, и на закуску — чат. Этот вариант оказался самым жестким, так как заключался даже не в том, что были сложности с битриксом. Чат на перле, написанный в 1996 году с вставками 2002 года, сверстанный в том же стиле (frameset-ы, ни одного стиля, прописанные в тегах свойства), данные в txt-файлах, ни единого намека на шаблоны, вывод сплошным потоком, хорошо хоть функции были какие-то — все это, знаете ли, вдохновляло. Да и его внешний вид вызывал желание закрыть страницу, но тут уж о вкусах не спорят — пользователям нравится, и ладно. Делать вставки туда пришлось единственным способом — консольно запускать php, выполнять скрипт и выводить результат. После выполнения битриксовских шапки или подвала скрипт заканчивается, а следовательно, буфер выводится в поток, и проблем с этим не возникает. Единственная тонкость заключалась в том, что так как скрипт выполнялся консольно, приходилось подключать битриксовские начальный и конечный скрипты по-особому, впрочем, об этом вполне сносно рассказывает официальная документация. Основные проблемы с чатом возникли из-за его ужасающей структуры и почтенного возраста, а также из-за того, что я видел перл в первый раз в жизни. Впечатлился настолько, что буду теперь с опаской открывать перловые скрипты. В результате получилось такое страшное чудовище, что без слез не взглянешь — пришлось добавлять отдельный фрейм и выводить шапку туда. И еще наплодить несколько хаков, когда перловый скрипт вызывает php-скрипт, который внутри себя вызывает еще один перловый… После окончания работы хотелось вымыть руки и сдать работу в Кунсткамеру. govnokod.ru отдыхает.
Вот так проходила одна из интеграций с битриксом. Удивлен, кстати, что несмотря на его популярность, нет обширных материалов по его настройке под себя и затачиванию напильником — находил только отдельные обрывки. А уж тем более, его интеграции в сторонние форумы, ибо его родной форум явно другим уступает. Если есть чем поделиться в этом плане — милости прошу, будет интересно узнать.
Отдельное спасибо bioroot за подсказки по perl-у и моральную поддержку в нелегком деле.
Хотя, для меня в этом плане довольно удивительно, что, например, форумы, которые по своему смыслу скорее будут прилагаться к порталам, не предоставляют средств интеграции или хотя бы не предусматривают потенциальную возможность в своем коде. По-моему, не так уж сложно вынести шаблоны отдельно, конфиги и инициализационный код так, чтобы его можно было легко подключить из других мест. Да и набившие оскомину глобальные переменные — разве сложно сделать под них отдельный namespace или отдельный класс со статическими переменными? Ну если уж совсем без них не обойтись, так можно хотя бы их в угол положить, чтобы не воняли, и периодически доставать, когда необходимы. Однако, логика берет свое — код написан давно, никто не хочет в нем копаться, работает — не лезь, да и бизнес тоже не поймет — на что это вы время тратите? Вы лучше реализуйте новую фичу, а я с нее бабок нарублю. Так оно и живет. Но, довольно, граф, вы увлеклись. К делу.
Битрикс? Очень приятно.
В один из прекрасных дней я получил задание по новому для себя проекту. Портал был написан довольно давно, поддерживался с тех пор на уровне администрирования, а требовалось вдохнуть в него новую жизнь. Задачи ставил человек, довольно далекий от программирования и IT в целом, а потому они сводились, в основном, на то, чтобы изменить пункты меню, поменять внешний вид и прочее. Правда, все задачи обсуждению почти не подлежали и должны были быть выполнены любой ценой. Меня запросто могли совершенно искренне спросить: «А почему нельзя искать сразу и на портале, и на форуме? Там же вон есть форма поиска, что же она не везде ищет? А как бы нам сделать, чтобы она везде искала?». И такое невинное, с их точки зрения, задание пришлось бы реализовывать. Посмотрел я на объем работ и пошел знакомиться проектом.
Увидел портал на битриксе. Обычный такой информационный портал, с невеликим функционалом, довольно старый. К порталу как отдельные продукты прилагались фотогалерея, форум и чат, которые по оформлению издалека напоминали портал, но не были с ним никак интегрированы. По всем элементам портала, кроме прочего, стояла одинаковая задача — привести их к одному внешнему виду, а именно — добавить стандартные шапку и подвал от битрикса. Если бы мы жили в эпоху благоденствия, где все люди были бы равны, колбаса бы стоила 2р.20коп., а все программные продукты были бы написаны с учетом последних веяний ООП, задача бы превращалась в легкую познавательную прогулку по шаблонам этих продуктов и десятиминутное написание унаследованного класса или подключение шаблонов. Но тогда было бы скучно жить. Как обычно, битрикс развлек меня по полной.
Анализ угрозы.
Итак, сначала небольшая матчасть для тех, кто по каким-то счастливым причинам еще не успел познакомиться с битриксом изнутри. Логика и представление в нем разнесены только в компонентах, ядро же в целом сохранило свою структуру с более ранних версий, а потому там все вперемешку.
Типовая страница на битриксе выглядит так:
<?require_once $_SERVER['DOCUMENT_ROOT'].'/bitrix/header.php'?>
Пользовательский скрипт.
<?require_once $_SERVER['DOCUMENT_ROOT'].'/bitrix/footer.php'?>
При этом в шапке и подвале страницы идет логика битрикса, в том числе и рендер шаблонов. Для того, чтобы можно было в пользовательском скрипте писать логику, которая будет управлять отображением в шаблоне, битрикс использует так называемые функции отложенного вызова. О них уже неоднократно писалось (например, здесь или в документации битрикса), а потому не буду лишний раз распространяться. Для работы таких функций требуется буферизация, а потому в вызове шапки открывается буфер для поглощения содержимого, а в подвале буфер закрывается, и его содержимое вываливается в поток.
Битрикс в своем роде уникален, и, когда хочешь понять, как работает какая-нибудь его часть, обычно лучше сразу лезть в код. Если сразу не стошнит, там можно подчас можно обнаружить очень интересные вещи. Документация — это хорошо, но не всегда там можно найти то, что требуется.А так как для интеграции с другими продуктами битрикс не предназначен (я не имею в виду 1С), то допиливать это придется руками. Итак, надели противогаз и ныряем.
Заплываем недалеко — в файл /bitrix/modules/main/include/prolog_before.php. И видим там блок кода, который отвечает за буферизацию:
if(COption::GetOptionString("main", "buffer_content", "Y")=="Y" && (!defined("BX_BUFFER_USED") || BX_BUFFER_USED!==true))
{
ob_start(Array(&$APPLICATION, "EndBufferContent"));
$APPLICATION->buffered = true;
define("BX_BUFFER_USED", true);
}
<Оффтоп>
Кстати, в 7 версии можно после этого еще увидеть такую строку:
register_shutdown_function(create_function('', 'while(@ob_end_flush());'));
За что люблю битрикс, так за то, что не дает расслабиться и все время держит в тонусе.
</Оффтоп>
И возникает интересный вопрос. Оказывается, есть некоторая настройка buffer_content, которая сигнализирует о том, нужно ли буферизовать содержимое. Примечательно в ней то, что она не настраивается через админку, да и в стандартной нулевой базе (если установить чистый битрикс) отсутствует. Поэтому берется значение по умолчанию, в данном случае, «Y». А что же будет, если ее добавить и установить в «N»? Пробираемся в базу, в таблицу b_option и заводим ее там. Потом чистим папку /bitrix/managed_cache и пробуем писать скрипты. Все, больше содержимое не буферизуется.Правда, теряется возможность менять из страницы внешний вид шапки, например, title. Но тут приходится выбирать, что важнее.
Установить настройку можно и методами самого битрикса, а именно методом COption::SetOptionString. Однако, будьте осторожны, работает он не так, как можно предположить. Мне казалось логичным поведение, если настройки с его помощью устанавливаются на время выполнения скрипта аналогично функции ini_set. Однако этот метод как раз-таки устанавливает настройку прямо в базе. Представляете, так бы работал ini_set — включил там error_reporting(E_ALL) для своего скрипта — и весь хостинг лег. Итак, научились включать/отключать буферизацию, пошли дальше.
Разминка.
Первый клиент на очереди — галерея.
Здесь случай довольно несложный — достаточно просто подключить шапку. Правда, из-за пристрастия к глобальным переменным со стороны обоих продуктов, здесь случился любовный треугольник: битрикс, фотогалерея и глобальная переменная $USER. А потому пришлось убирать ее unset-ом. Ситуация, правда, довольно типичная при интеграции продуктов, которые почему-то упорно отказываются использовать ООП и инкапсуляцию. Ну, да бог их простит. С подвалом все тоже довольно просто — достаточно только вытянуть глобальные переменные (здесь понадобился только $APPLICATION) через global $varname, а то битрикс обидится и упадет: он-то думал, что раз в шапке такую переменную объявил, то и в подвале ее найдет, ан нет.
Суровый форум.
Второй случай более запущенный — форум vbullentin. Примечателен он тем, что, во-первых, платный, а потому в код с автогеном не полезешь, а во-вторых, тем, что его писали тоже суровые мужики с бородами (кто захочет прочувствовать всю их мощь, можете попробовать разобраться в скрипте поиска по форуму).
В шаблон прописать подключение скрипта не вышло, ибо шаблонизатор тут свой и php-код он не выполняет. Единственное, что он может — это заменить одну php-переменную другой. Пришлось пользоваться этим, раз другого нет. Для заполнения таких переменных форум позволяет писать хаки (hooks) на php, а затем исполнять их по определенным событиям (eval-ом, естественно). Одно из событий вполне подходило, так что вполне было можно цеплять шапку туда. Проблема заключалась в том, что хак исполнялся до вывода шаблона, а потому надо было буферизовать вывод битриксовской шапки. То есть, клин клином вышибают. Я сначала долго пробовал по-хорошему, честно пытался закрыть открытый битриксом буфер, но все мои попытки оказались тщетными — тут мне не удалось его перебороть. Если у кого-то получится, выложите, пожалуйста — мне интересно посмотреть, как оно укрощается.
Поэтому я решил пойти на кардинальные меры и отключить буферизацию с помощью параметра buffer_content способом, который описал выше, если идет рендер форума. То есть в скрипт /bitrix/php_interface/init.php пришлось добавить такой код:
if(strpos($APPLICATION->GetCurDir(), '/forum/') === false)
COption::SetOptionString("main", "buffer_content", "Y",'','ru');
else
COption::SetOptionString("main", "buffer_content", "N",'','ru');
Конечно, криво и неприятно, но зато работает. Теперь можно было просто буферизовать и шапку, и подвал до рендера шаблонов самого форума. А именно:
ob_start();
include('bitrix/header.php');
$bitrixheader = ob_get_clean();
Аналогично и с подвалом. А затем вывести переменную $bitrixHeader в шаблон. Конечно, и здесь не обошлось без unset-а глобальных переменных, но это уже мелочи.
Действие 3. Те же и perl.
Ну, и на закуску — чат. Этот вариант оказался самым жестким, так как заключался даже не в том, что были сложности с битриксом. Чат на перле, написанный в 1996 году с вставками 2002 года, сверстанный в том же стиле (frameset-ы, ни одного стиля, прописанные в тегах свойства), данные в txt-файлах, ни единого намека на шаблоны, вывод сплошным потоком, хорошо хоть функции были какие-то — все это, знаете ли, вдохновляло. Да и его внешний вид вызывал желание закрыть страницу, но тут уж о вкусах не спорят — пользователям нравится, и ладно. Делать вставки туда пришлось единственным способом — консольно запускать php, выполнять скрипт и выводить результат. После выполнения битриксовских шапки или подвала скрипт заканчивается, а следовательно, буфер выводится в поток, и проблем с этим не возникает. Единственная тонкость заключалась в том, что так как скрипт выполнялся консольно, приходилось подключать битриксовские начальный и конечный скрипты по-особому, впрочем, об этом вполне сносно рассказывает официальная документация. Основные проблемы с чатом возникли из-за его ужасающей структуры и почтенного возраста, а также из-за того, что я видел перл в первый раз в жизни. Впечатлился настолько, что буду теперь с опаской открывать перловые скрипты. В результате получилось такое страшное чудовище, что без слез не взглянешь — пришлось добавлять отдельный фрейм и выводить шапку туда. И еще наплодить несколько хаков, когда перловый скрипт вызывает php-скрипт, который внутри себя вызывает еще один перловый… После окончания работы хотелось вымыть руки и сдать работу в Кунсткамеру. govnokod.ru отдыхает.
Заключение.
Вот так проходила одна из интеграций с битриксом. Удивлен, кстати, что несмотря на его популярность, нет обширных материалов по его настройке под себя и затачиванию напильником — находил только отдельные обрывки. А уж тем более, его интеграции в сторонние форумы, ибо его родной форум явно другим уступает. Если есть чем поделиться в этом плане — милости прошу, будет интересно узнать.
Отдельное спасибо bioroot за подсказки по perl-у и моральную поддержку в нелегком деле.