0. Приемы разработки На этот раз речь пойдет о разработке компонентов 2.0. Документации и примеров вполне достаточно, чтобы начать создавать свои компоненты, однако есть некоторые тонкости программирования, которые необходимо учитывать. 1. Защита от прямого вызова Во всех PHP-файлах (component.php, .description.php, .parameters.php, template.php и др.) компонента первая строка должна быть такой: if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die(); Данная строка гарантирует подключение компонента после подключения пролога. В противном случае при вызове файла напрямую через HTTP-запрос получим Fatal Error и тем самым сообщим недоброжелателю DOCUMENT_ROOT. 2. Кеширование Для упрощения разработки компонентов 2.0, использующих кеширование данных и выводимого HTML, был добавлен метод StartResultCache. Эта функция: - учитывает параметры компонента CACHE_TYPE (тип кеширования) и CACHE_TIME (время кеширования); - кеширует весь выводимый шаблоном HTML, а также массив $arResult. Пример использования StartResultCache if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die(); //Module if (!CModule::IncludeModule("learning")) { ShowError(GetMessage("LEARNING_MODULE_NOT_FOUND")); return; } if($this->StartResultCache(false, $USER->GetGroups())) { /*... Выборка данных в массив $arResult*/ //Подключение шаблона $this->IncludeComponentTemplate(); } Этот код работает правильно, однако он неоптимален. Подключение модуля происходит всегда, даже если есть валидный кеш, поэтому CModule::IncludeModule следует включить в кешируемую область. Пример более рационального использования StartResultCache if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die(); if($this->StartResultCache(false, $USER->GetGroups())) { //Module if (!CModule::IncludeModule("learning")) { $this->AbortResultCache(); ShowError(GetMessage("LEARNING_MODULE_NOT_FOUND")); return; } /*... Выборка данных в массив $arResult*/ //Подключение шаблона $this->IncludeComponentTemplate(); } Необходимо также учитывать, что в идентификатор кеша метод StartResultCache "подмешивает" адрес текущей страницы. Если компонент будет использоваться, например, в шаблоне сайта (т.е. будет вызываться на всех страницах веб-ресурса), то количество созданных файлов кеша будет соответствовать количеству страниц на сайте. Для кеширования таких компонентов следует использовать стандартный класс CPHPCache с идентификатором, независящим от текущей страницы. Пример использования класса CPHPCache есть в документации. 3. Безопасность данных в массиве $arResult Для повышения безопасности шаблонов и уменьшения головной боли разработчика шаблонов в массиве $arResult должны содержаться уже обработанные данные (через htmlspecialchars или $cdbresult->GetNext()). Если необходимы «сырые» данные, следует помещать их в отдельные поля, начинающиеся с тильды ($arResult["~NAME"]). Метод CDBResult::GetNext добавляет поля с тильдой автоматически. 4. Постраничная навигация Постраничная навигация реализуется с помощью системного компонента system.pagenavigation. Этот компонент можно вызвать непосредственно с помощью IncludeComponent, либо через метод-обертку CDBResult::GetPageNavString. Входные параметры компонента system.pagenavigation: string NAV_TITLE - название постраничной навигации; CDBResult NAV_RESULT - ссылка на объект типа CDBResult; bool SHOW_ALWAYS - показывать постраничную навигацию, если все записи помещаются на одну страницу. Метод CDBResult::GetPageNavString подключает компонент system.pagenavigation и возвращает HTML постраничной навигации. Сигнатура функции: CDBResult::GetPageNavString( string [I]navigationTitle[/I], string [I]templateName[/I] = "", bool [I]showAlways[/I]=false ) navigationTitle – название постраничной навигации; templateName – шаблон постраничной навигации; showAlways – показывать постраничную навигацию, если все записи помещаются на одну страницу. Типичный пример использования CDBResult::GetPageNavString В логике компонента (component.php): CPageOption::SetOptionString("main", "nav_page_in_session", "N"); // убирает запоминание страниц $obTicket = CTicket::GetList(Array(), Array()); //Выбираем записи $obTicket->NavStart(20); //Разбиваем по 20 элементов на страницу $arResult["NAV_STRING"] = $obTicket->GetPageNavString("Обращения"); //HTML постраничного вывода $arResult["NAV_RESULT"] = $obTicket; //ссылка на объект В шаблоне (template.php): <?=$arResult["NAV_STRING"]?> Ссылка на объект в $arResult["NAV_RESULT"] нужна для того, чтобы в шаблоне компонента была возможность подключить system.pagenavigation. 5. Идентификаторы картинок API-методы возвращают только идентификатор картинки, которого в шаблоне компонента будет недостаточно. Необходимы все данные: высота, ширина, путь к файлу. Вместо стандартной функции CFile::GetByID рекомендую использовать новый метод CFile::GetFileArray. Метод CFile::GetFileArray($FILE_ID) возвращает массив со всеми данными о файле (см. описание полей файлов), а также путь к файлу (в ключе SRC) относительно корня сайта. Если файл с идентификатором $FILE_ID не был найден, метод возвращает false. Компонент: $arResult["IMAGE"] = CFile::GetFileArray($arElement["IMAGE_ID"]); Шаблон: <?if ($arResult["IMAGE"] !== false):?> <img src="<?=$arResult["IMAGE"]["SRC"]?>" width="<?=$arResult["IMAGE"]["WIDTH"]?>" height="<?=$arResult["IMAGE"]["HEIGHT"]?>" border="0" /> <?endif?> 6. Защита форм, ссылок "Удалить" и других действий, зависящих от прав доступа текущего пользователя Представим, что на вашем сайте выводится компонент новостей, в котором есть возможность удалить новость. Ссылка на удаление выглядит таким образом: www.mysite.ru/news/detail.php?ID=123&delete=Y Далее недоброжелатель помещает эту ссылку в форуме, например, в теге <img src=" www.mysite.ru/news/detail.php?ID=123&delete=Y" />. Зайдя на этот форум и будучи авторизованным на сайте www.mysite.ru , вы, ничего неподозревая, удалите новость c идентификатором 123. Т.о. даже несмотря на проверку прав доступа в компоненте, недображелатель сможет удалить новость на вашем сайте. Для защиты от таких атак следует дополнительно проверять идентификатор сессии текущего пользователя, передавая его в ссылке или в скрытом поле формы. Для этого в продукте есть ряд функций в помощь: string bitrix_sessid() - возвращает идентификатор сессии, предварительно обработанный функцией md5. bool check_bitrix_sessid($varname='sessid') - возвращает true, если верно условие $_REQUEST[$varname] == bitrix_sessid(), иначе false. string bitrix_sessid_get($varname='sessid') - возвращает строку вида $varname=идентификатор сессии string bitrix_sessid_post($varname='sessid') - возвращает строку вида <input type="hidden" name="$varname" id="$varname" value="идентификатор сесии" /> Передача идентификатора сессии в форме <form method="post" action="/news/add.php"> <?=bitrix_sessid_post()?> Title: <input type="text" name="NAME" size="40" maxlength="255" value=""> </form> Передача идентификатора в ссылке <a href="/news/detail.php?ID=123&delete=Y&<?=bitrix_sessid_get()?>">Удалить</a> Проверка сессии if($arResult["Perm"]>=BLOG_PERMS_MODERATE && intval($_GET["delete_comment_id"])>0 && check_bitrix_sessid()) { /*Удаление комментария*/ } Герасимюк Антон
Компания АКРИТ
Компоненты 2.
0. Приемы разработки
На этот раз речь пойдет о разработке компонентов 2.0. Документации и примеров вполне достаточно, чтобы начать создавать свои компоненты, однако есть некоторые тонкости программирования, которые необходимо учитывать.
1. Защита от прямого вызова
Во всех PHP-файлах (component.php, .description.php, .parameters.php, template.php и др.) компонента первая строка должна быть такой:
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
Данная строка гарантирует подключение компонента после подключения пролога. В противном случае при вызове файла напрямую через HTTP-запрос получим Fatal Error и тем самым сообщим недоброжелателю DOCUMENT_ROOT.
2. Кеширование
Для упрощения разработки компонентов 2.0, использующих кеширование данных и выводимого HTML, был добавлен метод StartResultCache. Эта функция:
- учитывает параметры компонента CACHE_TYPE (тип кеширования) и CACHE_TIME (время кеширования);
- кеширует весь выводимый шаблоном HTML, а также массив $arResult.
Пример использования StartResultCache
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
//Module
if (!CModule::IncludeModule("learning"))
{
ShowError(GetMessage("LEARNING_MODULE_NOT_FOUND"));
return;
}
if($this->StartResultCache(false, $USER->GetGroups()))
{
/*... Выборка данных в массив $arResult*/
//Подключение шаблона
$this->IncludeComponentTemplate();
}
Этот код работает правильно, однако он неоптимален. Подключение модуля происходит всегда, даже если есть валидный кеш, поэтому CModule::IncludeModule следует включить в кешируемую область.
Пример более рационального использования StartResultCache
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
if($this->StartResultCache(false, $USER->GetGroups()))
{
//Module
if (!CModule::IncludeModule("learning"))
{
$this->AbortResultCache();
ShowError(GetMessage("LEARNING_MODULE_NOT_FOUND"));
return;
}
/*... Выборка данных в массив $arResult*/
//Подключение шаблона
$this->IncludeComponentTemplate();
}
Необходимо также учитывать, что в идентификатор кеша метод StartResultCache "подмешивает" адрес текущей страницы. Если компонент будет использоваться, например, в шаблоне сайта (т.е. будет вызываться на всех страницах веб-ресурса), то количество созданных файлов кеша будет соответствовать количеству страниц на сайте. Для кеширования таких компонентов следует использовать стандартный класс CPHPCache с идентификатором, независящим от текущей страницы. Пример использования класса CPHPCache есть в документации.
3. Безопасность данных в массиве $arResult
Для повышения безопасности шаблонов и уменьшения головной боли разработчика шаблонов в массиве $arResult должны содержаться уже обработанные данные (через htmlspecialchars или $cdbresult->GetNext()).
Если необходимы «сырые» данные, следует помещать их в отдельные поля, начинающиеся с тильды ($arResult["~NAME"]). Метод CDBResult::GetNext добавляет поля с тильдой автоматически.
4. Постраничная навигация
Постраничная навигация реализуется с помощью системного компонента system.pagenavigation. Этот компонент можно вызвать непосредственно с помощью IncludeComponent, либо через метод-обертку CDBResult::GetPageNavString.
Входные параметры компонента system.pagenavigation:
string NAV_TITLE - название постраничной навигации;
CDBResult NAV_RESULT - ссылка на объект типа CDBResult;
bool SHOW_ALWAYS - показывать постраничную навигацию, если все записи помещаются на одну страницу.
Метод CDBResult::GetPageNavString подключает компонент system.pagenavigation и возвращает HTML постраничной навигации.
Сигнатура функции:
CDBResult::GetPageNavString(
string [I]navigationTitle[/I],
string [I]templateName[/I] = "",
bool [I]showAlways[/I]=false
)
navigationTitle – название постраничной навигации;
templateName – шаблон постраничной навигации;
showAlways – показывать постраничную навигацию, если все записи помещаются на одну страницу.
Типичный пример использования CDBResult::GetPageNavString
В логике компонента (component.php):
CPageOption::SetOptionString("main", "nav_page_in_session", "N"); // убирает запоминание страниц
$obTicket = CTicket::GetList(Array(), Array()); //Выбираем записи
$obTicket->NavStart(20); //Разбиваем по 20 элементов на страницу
$arResult["NAV_STRING"] = $obTicket->GetPageNavString("Обращения"); //HTML постраничного вывода
$arResult["NAV_RESULT"] = $obTicket; //ссылка на объект
В шаблоне (template.php):
<?=$arResult["NAV_STRING"]?>
Ссылка на объект в $arResult["NAV_RESULT"] нужна для того, чтобы в шаблоне компонента была возможность подключить system.pagenavigation.
5. Идентификаторы картинок
API-методы возвращают только идентификатор картинки, которого в шаблоне компонента будет недостаточно. Необходимы все данные: высота, ширина, путь к файлу. Вместо стандартной функции CFile::GetByID рекомендую использовать новый метод CFile::GetFileArray.
Метод CFile::GetFileArray($FILE_ID) возвращает массив со всеми данными о файле (см. описание полей файлов), а также путь к файлу (в ключе SRC) относительно корня сайта. Если файл с идентификатором $FILE_ID не был найден, метод возвращает false.
Компонент:
$arResult["IMAGE"] = CFile::GetFileArray($arElement["IMAGE_ID"]);
Шаблон:
<?if ($arResult["IMAGE"] !== false):?>
<img src="<?=$arResult["IMAGE"]["SRC"]?>" width="<?=$arResult["IMAGE"]["WIDTH"]?>" height="<?=$arResult["IMAGE"]["HEIGHT"]?>" border="0" />
<?endif?>
6. Защита форм, ссылок "Удалить" и других действий, зависящих от прав доступа текущего пользователя
Представим, что на вашем сайте выводится компонент новостей, в котором есть возможность удалить новость. Ссылка на удаление выглядит таким образом: www.mysite.ru/news/detail.php?ID=123&delete=Y Далее недоброжелатель помещает эту ссылку в форуме, например, в теге <img src=" www.mysite.ru/news/detail.php?ID=123&delete=Y" />. Зайдя на этот форум и будучи авторизованным на сайте www.mysite.ru , вы, ничего неподозревая, удалите новость c идентификатором 123. Т.о. даже несмотря на проверку прав доступа в компоненте, недображелатель сможет удалить новость на вашем сайте. Для защиты от таких атак следует дополнительно проверять идентификатор сессии текущего пользователя, передавая его в ссылке или в скрытом поле формы.
Для этого в продукте есть ряд функций в помощь:
string bitrix_sessid() - возвращает идентификатор сессии, предварительно обработанный функцией md5.
bool check_bitrix_sessid($varname='sessid') - возвращает true, если верно условие $_REQUEST[$varname] == bitrix_sessid(), иначе false.
string bitrix_sessid_get($varname='sessid') - возвращает строку вида $varname=идентификатор сессии
string bitrix_sessid_post($varname='sessid') - возвращает строку вида <input type="hidden" name="$varname" id="$varname" value="идентификатор сесии" />
Передача идентификатора сессии в форме
<form method="post" action="/news/add.php">
<?=bitrix_sessid_post()?>
Title: <input type="text" name="NAME" size="40" maxlength="255" value="">
</form>
Передача идентификатора в ссылке
<a href="/news/detail.php?ID=123&delete=Y&<?=bitrix_sessid_get()?>">Удалить</a>
Проверка сессии
if($arResult["Perm"]>=BLOG_PERMS_MODERATE && intval($_GET["delete_comment_id"])>0 && check_bitrix_sessid())
{
/*Удаление комментария*/
}
Герасимюк Антон