Не буду особо размусоливать тему. В битриксе есть поддержка ЧПУ для каталога, но в случае вложенных разделов добиться правильного ЧПУ типа "Раздел1/Подраздел2/Подраздел3" стандартным функционалом невозможно. В сети есть несколько вариантов реализации данной задумки - хочу поделится своей.
Сразу скажу. Описанный здесь вариант потребует небольшого изменения в системном файле ядра (да это плохо, но лучше внести пару строк в файл чем городить множество костылей) и использования своего компонента для каталога (чтобы определить просматриваемый раздел). Однако работать все будет быстро и нормально с другими компонентами в системе, также по сути не придется городить для них дополнительные костыли.
И так, начнем. Для создания правила построения урла в настройках инфоблока указываются системные переменные. Так вот идея заключается в том чтобы внести новую переменную в общий список и не заморачиватся больше нигде с правилами построения полного пути. К сожалению, никаких перехватчиков в системе нет для этого дела, так что придется править системный файл. Решение делалось на версии 11.5.7 битрикса.
Также, хочу заметить, я не буду описывать как идет сохранение и определение урлов. Вся логика доступна в созданном мной модуле (создание таблицы БД, и перехват событий изменения разделов). Сам файл приложен к данному посту. Здесь же опишу те шаги что нужно сделать чтобы все заработало как нужно. Также для разделов каталога должны быть указаны символьные коды чтобы вся идея с ЧПУ заработала.
Сори за смесь кода и текста - при использовании форматирования кода визуальный редактор делает кучу...
Шаг первый.
Качаем и устанавливаем модуль it_catalogadds. Полные пути строятся только для инфоблока типа 'catalog'. Если нужно чтобы обрабатывался другой тип просто добавьте его в массив проверки в файле "it_catalogadds/classes/mysql/it_ca_sectionpath.php" в стрке 44.
Шаг второй.
Для инфоблока указываем правила для построения урлов:
URL страницы раздела: #SITE_DIR#/catalog/#SECTION_PATH#/
URL страницы детального просмотра: #SITE_DIR#/catalog/#SECTION_PATH#/#ELEMENT_ID#/
(можно также использовать переменную #ELEMENT_CODE#)
Шаг третий.
В файл "/bitrix/modules/iblock/classes/general/iblock.php" в методе "ReplaceDetailUrl" добавляем:
В строке 2482 идет определение массива который используется для тех самых системных переменных по которым строится правило назначения пути. Нам нужно добавить в конце новый элемент, в моем случае это "#SECTION_PATH#".
static $arSearch = array(
/*Theese come from GetNext*/
"#SITE_DIR#",
"#ID#",
"#CODE#",
"#EXTERNAL_ID#",
"#IBLOCK_TYPE_ID#",
"#IBLOCK_ID#",
"#IBLOCK_CODE#",
"#IBLOCK_EXTERNAL_ID#",
/*And theese was born during components 2 development*/
"#ELEMENT_ID#",
"#ELEMENT_CODE#",
"#SECTION_ID#",
"#SECTION_CODE#",
/*And theese by itargency */
"#SECTION_PATH#"
);
Далее нужно добавить обработчик для нашего нового правила. Делаем это в конце метода после всех дефолтных, чтобы все обрабатывалось правильно.
$SECTION_PATH = "";
if (strpos($url, "#SECTION_PATH#") !== false && CModule::IncludeModule("it_catalogadds")) {
$SECTION_ID = ($arrType === "S") ? intval($arr["ID"]) : intval($arr["IBLOCK_SECTION_ID"]);
$SECTION_PATH = ITCASectionPath::getFullPath(intval($arr["IBLOCK_ID"]), $SECTION_ID);
}
$arReplace[] = $SECTION_PATH;
Здесь появляется класс ITCASectionPath - именно он отвечает у меня за всю реализацию. В итоге метод должен выглядеть так:
function ReplaceDetailUrl($url, $arr, $server_name = false, $arrType = false)
{
global $DB;
static $arSectionCache = array();
if($server_name)
{
$url = str_replace("#LANG#", $arr["LANG_DIR"], $url);
if((defined("ADMIN_SECTION") && ADMIN_SECTION===true) || !defined("BX_STARTED"))
{
static $lcache;
if(!is_array($lcache))
$lcache = array();
if(!is_set($lcache, $arr["LID"]))
{
$db_lang = CLang::GetByID($arr["LID"]);
$arLang = $db_lang->Fetch();
$lcache[$arr["LID"]] = $arLang;
}
$arLang = $lcache[$arr["LID"]];
$url = str_replace("#SITE_DIR#", $arLang["DIR"], $url);
$url = str_replace("#SERVER_NAME#", $arLang["SERVER_NAME"], $url);
}
else
{
$url = str_replace("#SITE_DIR#", SITE_DIR, $url);
$url = str_replace("#SERVER_NAME#", SITE_SERVER_NAME, $url);
}
}
if(strpos($url, "#PRODUCT_URL#") !== false)
$url = str_replace("#PRODUCT_URL#", CIBlock::_GetProductUrl($arr["ID"], $arr["IBLOCK_ID"], $server_name, $arrType), $url);
static $arSearch = array(
/*Theese come from GetNext*/
"#SITE_DIR#",
"#ID#",
"#CODE#",
"#EXTERNAL_ID#",
"#IBLOCK_TYPE_ID#",
"#IBLOCK_ID#",
"#IBLOCK_CODE#",
"#IBLOCK_EXTERNAL_ID#",
/*And theese was born during components 2 development*/
"#ELEMENT_ID#",
"#ELEMENT_CODE#",
"#SECTION_ID#",
"#SECTION_CODE#",
/*And theese by itargency */
"#SECTION_PATH#"
);
$arReplace = array(
$arr["LANG_DIR"],
intval($arr["ID"]) > 0? intval($arr["ID"]): "",
urlencode(isset($arr["~CODE"])? $arr["~CODE"]: $arr["CODE"]),
urlencode(isset($arr["~EXTERNAL_ID"])? $arr["~EXTERNAL_ID"]: $arr["EXTERNAL_ID"]),
urlencode(isset($arr["~IBLOCK_TYPE_ID"])? $arr["~IBLOCK_TYPE_ID"]: $arr["IBLOCK_TYPE_ID"]),
intval($arr["IBLOCK_ID"]) > 0? intval($arr["IBLOCK_ID"]): "",
urlencode(isset($arr["~IBLOCK_CODE"])? $arr["~IBLOCK_CODE"]: $arr["IBLOCK_CODE"]),
urlencode(isset($arr["~IBLOCK_EXTERNAL_ID"])? $arr["~IBLOCK_EXTERNAL_ID"]: $arr["IBLOCK_EXTERNAL_ID"]),
);
if($arrType === "E")
{
$arReplace[] = intval($arr["ID"]) > 0? intval($arr["ID"]): "";
$arReplace[] = urlencode(isset($arr["~CODE"])? $arr["~CODE"]: $arr["CODE"]);
#Deal with symbol codes
$SECTION_CODE = "";
$SECTION_ID = intval($arr["IBLOCK_SECTION_ID"]);
if(strpos($url, "#SECTION_CODE#") !== false && $SECTION_ID > 0)
{
if(!array_key_exists($SECTION_ID, $arSectionCache))
{
$res = $DB->Query("SEL ECT CODE FR OM b_iblock_section WH ERE ID = ".$SECTION_ID);
$arSectionCache[$SECTION_ID] = $res->Fetch();
}
if(is_array($arSectionCache[$SECTION_ID]))
$SECTION_CODE = $arSectionCache[$SECTION_ID]["CODE"];
}
$arReplace[] = $SECTION_ID > 0? $SECTION_ID: "";
$arReplace[] = urlencode($SECTION_CODE);
}
elseif($arrType === "S")
{
$arReplace[] = "";
$arReplace[] = "";
$arReplace[] = intval($arr["ID"]) > 0? intval($arr["ID"]): "";
$arReplace[] = urlencode(isset($arr["~CODE"])? $arr["~CODE"]: $arr["CODE"]);
}
else
{
$arReplace[] = intval($arr["ELEMENT_ID"]) > 0? intval($arr["ELEMENT_ID"]): "";
$arReplace[] = urlencode(isset($arr["~ELEMENT_CODE"])? $arr["~ELEMENT_CODE"]: $arr["ELEMENT_CODE"]);
$arReplace[] = intval($arr["IBLOCK_SECTION_ID"]) > 0? intval($arr["IBLOCK_SECTION_ID"]): "";
$arReplace[] = urlencode(isset($arr["~SECTION_CODE"])? $arr["~SECTION_CODE"]: $arr["SECTION_CODE"]);
}
$SECTION_PATH = "";
if (strpos($url, "#SECTION_PATH#") !== false && CModule::IncludeModule("it_catalogadds")) {
$SECTION_ID = ($arrType === "S") ? intval($arr["ID"]) : intval($arr["IBLOCK_SECTION_ID"]);
$SECTION_PATH = ITCASectionPath::getFullPath(intval($arr["IBLOCK_ID"]), $SECTION_ID);
}
$arReplace[] = $SECTION_PATH;
$url = str_replace($arSearch, $arReplace, $url);
return preg_replace("'(?<!:)/+'s", "/", $url);
}
Шаг четвертый.
Копируем компонент catalog в наше пространство имен.
1. В файл "/bitrix/components/ваше название/catalog/component.php" добавляем обработку того что у нас за урл. Заменяем блок отвечающих за обработку ЧПУ (условие if($arParams["SEF_MODE"] == "Y")) нашим собственным:
Код
if($arParams["SEF_MODE"] == "Y")
{
$arVariables = array();
$arUrlTemplates = CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404, $arParams["SEF_URL_TEMPLATES"]);
$arVariableAliases = CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404, $arParams["VARIABLE_ALIASES"]);
if (false !== strpos($arUrlTemplates['section'], '#SECTION_PATH#') || false !== strpos($arUrlTemplates['element'], '#SECTION_PATH#')) {
$componentPage = ITCASectionPath::parseComponentPath(
$arParams['IBLOCK_ID'],
$arParams["SEF_FOLDER"],
$arUrlTemplates,
$arVariables
);
} else {
$componentPage = CComponentEngine::ParseComponentPath(
$arParams["SEF_FOLDER"],
$arUrlTemplates,
$arVariables
);
}
$b404 = false;
if(!$componentPage)
{
$componentPage = "sections";
$b404 = true;
}
if(
$componentPage == "section"
&& isset($arVariables["SECTION_ID"])
&& intval($arVariables["SECTION_ID"])."" !== $arVariables["SECTION_ID"]
)
$b404 = true;
if($b404 && $arParams["SET_STATUS_404"]==="Y")
{
$folder404 = str_replace("\\", "/", $arParams["SEF_FOLDER"]);
if ($folder404 != "/")
$folder404 = "/".trim($folder404, "/ \t\n\r\0\x0B")."/";
if (substr($folder404, -1) == "/")
$folder404 .= "index.php";
if($folder404 != $APPLICATION->GetCurPage(true))
CHTTP::SetStatus("404 Not Found");
}
CComponentEngine::InitComponentVariables($componentPage, $arComponentVariables, $arVariableAliases, $arVariables);
$arResult = array(
"FOLDER" => $arParams["SEF_FOLDER"],
"URL_TEMPLATES" => $arUrlTemplates,
"VARIABLES" => $arVariables,
"ALIASES" => $arVariableAliases
);
}
По сути добавляется просто проверка присутствия в правиле нашей переменной #SECTION_PATH# и поручаем провести проверку нашему классу ITCASectionPath. По сути, при положительной проверке мы указываем системе что используем идентификатор раздела вместо переменной пути и отдаем дальнейшее стандартным обработчикам.
И если все сделали правильно мы получаем нормальные ЧПУ урлы для каталога (разделы и итемы), не заморачиваемся с другими компонентами и не грузим систему доп проверками.
Надеюсь это будет кому-то полезно.
Сразу скажу. Описанный здесь вариант потребует небольшого изменения в системном файле ядра (да это плохо, но лучше внести пару строк в файл чем городить множество костылей) и использования своего компонента для каталога (чтобы определить просматриваемый раздел). Однако работать все будет быстро и нормально с другими компонентами в системе, также по сути не придется городить для них дополнительные костыли.
И так, начнем. Для создания правила построения урла в настройках инфоблока указываются системные переменные. Так вот идея заключается в том чтобы внести новую переменную в общий список и не заморачиватся больше нигде с правилами построения полного пути. К сожалению, никаких перехватчиков в системе нет для этого дела, так что придется править системный файл. Решение делалось на версии 11.5.7 битрикса.
Также, хочу заметить, я не буду описывать как идет сохранение и определение урлов. Вся логика доступна в созданном мной модуле (создание таблицы БД, и перехват событий изменения разделов). Сам файл приложен к данному посту. Здесь же опишу те шаги что нужно сделать чтобы все заработало как нужно. Также для разделов каталога должны быть указаны символьные коды чтобы вся идея с ЧПУ заработала.
Сори за смесь кода и текста - при использовании форматирования кода визуальный редактор делает кучу...
Шаг первый.
Качаем и устанавливаем модуль it_catalogadds. Полные пути строятся только для инфоблока типа 'catalog'. Если нужно чтобы обрабатывался другой тип просто добавьте его в массив проверки в файле "it_catalogadds/classes/mysql/it_ca_sectionpath.php" в стрке 44.
Шаг второй.
Для инфоблока указываем правила для построения урлов:
URL страницы раздела: #SITE_DIR#/catalog/#SECTION_PATH#/
URL страницы детального просмотра: #SITE_DIR#/catalog/#SECTION_PATH#/#ELEMENT_ID#/
(можно также использовать переменную #ELEMENT_CODE#)
Шаг третий.
В файл "/bitrix/modules/iblock/classes/general/iblock.php" в методе "ReplaceDetailUrl" добавляем:
В строке 2482 идет определение массива который используется для тех самых системных переменных по которым строится правило назначения пути. Нам нужно добавить в конце новый элемент, в моем случае это "#SECTION_PATH#".
static $arSearch = array(
/*Theese come from GetNext*/
"#SITE_DIR#",
"#ID#",
"#CODE#",
"#EXTERNAL_ID#",
"#IBLOCK_TYPE_ID#",
"#IBLOCK_ID#",
"#IBLOCK_CODE#",
"#IBLOCK_EXTERNAL_ID#",
/*And theese was born during components 2 development*/
"#ELEMENT_ID#",
"#ELEMENT_CODE#",
"#SECTION_ID#",
"#SECTION_CODE#",
/*And theese by itargency */
"#SECTION_PATH#"
);
Далее нужно добавить обработчик для нашего нового правила. Делаем это в конце метода после всех дефолтных, чтобы все обрабатывалось правильно.
$SECTION_PATH = "";
if (strpos($url, "#SECTION_PATH#") !== false && CModule::IncludeModule("it_catalogadds")) {
$SECTION_ID = ($arrType === "S") ? intval($arr["ID"]) : intval($arr["IBLOCK_SECTION_ID"]);
$SECTION_PATH = ITCASectionPath::getFullPath(intval($arr["IBLOCK_ID"]), $SECTION_ID);
}
$arReplace[] = $SECTION_PATH;
Здесь появляется класс ITCASectionPath - именно он отвечает у меня за всю реализацию. В итоге метод должен выглядеть так:
function ReplaceDetailUrl($url, $arr, $server_name = false, $arrType = false)
{
global $DB;
static $arSectionCache = array();
if($server_name)
{
$url = str_replace("#LANG#", $arr["LANG_DIR"], $url);
if((defined("ADMIN_SECTION") && ADMIN_SECTION===true) || !defined("BX_STARTED"))
{
static $lcache;
if(!is_array($lcache))
$lcache = array();
if(!is_set($lcache, $arr["LID"]))
{
$db_lang = CLang::GetByID($arr["LID"]);
$arLang = $db_lang->Fetch();
$lcache[$arr["LID"]] = $arLang;
}
$arLang = $lcache[$arr["LID"]];
$url = str_replace("#SITE_DIR#", $arLang["DIR"], $url);
$url = str_replace("#SERVER_NAME#", $arLang["SERVER_NAME"], $url);
}
else
{
$url = str_replace("#SITE_DIR#", SITE_DIR, $url);
$url = str_replace("#SERVER_NAME#", SITE_SERVER_NAME, $url);
}
}
if(strpos($url, "#PRODUCT_URL#") !== false)
$url = str_replace("#PRODUCT_URL#", CIBlock::_GetProductUrl($arr["ID"], $arr["IBLOCK_ID"], $server_name, $arrType), $url);
static $arSearch = array(
/*Theese come from GetNext*/
"#SITE_DIR#",
"#ID#",
"#CODE#",
"#EXTERNAL_ID#",
"#IBLOCK_TYPE_ID#",
"#IBLOCK_ID#",
"#IBLOCK_CODE#",
"#IBLOCK_EXTERNAL_ID#",
/*And theese was born during components 2 development*/
"#ELEMENT_ID#",
"#ELEMENT_CODE#",
"#SECTION_ID#",
"#SECTION_CODE#",
/*And theese by itargency */
"#SECTION_PATH#"
);
$arReplace = array(
$arr["LANG_DIR"],
intval($arr["ID"]) > 0? intval($arr["ID"]): "",
urlencode(isset($arr["~CODE"])? $arr["~CODE"]: $arr["CODE"]),
urlencode(isset($arr["~EXTERNAL_ID"])? $arr["~EXTERNAL_ID"]: $arr["EXTERNAL_ID"]),
urlencode(isset($arr["~IBLOCK_TYPE_ID"])? $arr["~IBLOCK_TYPE_ID"]: $arr["IBLOCK_TYPE_ID"]),
intval($arr["IBLOCK_ID"]) > 0? intval($arr["IBLOCK_ID"]): "",
urlencode(isset($arr["~IBLOCK_CODE"])? $arr["~IBLOCK_CODE"]: $arr["IBLOCK_CODE"]),
urlencode(isset($arr["~IBLOCK_EXTERNAL_ID"])? $arr["~IBLOCK_EXTERNAL_ID"]: $arr["IBLOCK_EXTERNAL_ID"]),
);
if($arrType === "E")
{
$arReplace[] = intval($arr["ID"]) > 0? intval($arr["ID"]): "";
$arReplace[] = urlencode(isset($arr["~CODE"])? $arr["~CODE"]: $arr["CODE"]);
#Deal with symbol codes
$SECTION_CODE = "";
$SECTION_ID = intval($arr["IBLOCK_SECTION_ID"]);
if(strpos($url, "#SECTION_CODE#") !== false && $SECTION_ID > 0)
{
if(!array_key_exists($SECTION_ID, $arSectionCache))
{
$res = $DB->Query("SEL ECT CODE FR OM b_iblock_section WH ERE ID = ".$SECTION_ID);
$arSectionCache[$SECTION_ID] = $res->Fetch();
}
if(is_array($arSectionCache[$SECTION_ID]))
$SECTION_CODE = $arSectionCache[$SECTION_ID]["CODE"];
}
$arReplace[] = $SECTION_ID > 0? $SECTION_ID: "";
$arReplace[] = urlencode($SECTION_CODE);
}
elseif($arrType === "S")
{
$arReplace[] = "";
$arReplace[] = "";
$arReplace[] = intval($arr["ID"]) > 0? intval($arr["ID"]): "";
$arReplace[] = urlencode(isset($arr["~CODE"])? $arr["~CODE"]: $arr["CODE"]);
}
else
{
$arReplace[] = intval($arr["ELEMENT_ID"]) > 0? intval($arr["ELEMENT_ID"]): "";
$arReplace[] = urlencode(isset($arr["~ELEMENT_CODE"])? $arr["~ELEMENT_CODE"]: $arr["ELEMENT_CODE"]);
$arReplace[] = intval($arr["IBLOCK_SECTION_ID"]) > 0? intval($arr["IBLOCK_SECTION_ID"]): "";
$arReplace[] = urlencode(isset($arr["~SECTION_CODE"])? $arr["~SECTION_CODE"]: $arr["SECTION_CODE"]);
}
$SECTION_PATH = "";
if (strpos($url, "#SECTION_PATH#") !== false && CModule::IncludeModule("it_catalogadds")) {
$SECTION_ID = ($arrType === "S") ? intval($arr["ID"]) : intval($arr["IBLOCK_SECTION_ID"]);
$SECTION_PATH = ITCASectionPath::getFullPath(intval($arr["IBLOCK_ID"]), $SECTION_ID);
}
$arReplace[] = $SECTION_PATH;
$url = str_replace($arSearch, $arReplace, $url);
return preg_replace("'(?<!:)/+'s", "/", $url);
}
Шаг четвертый.
Копируем компонент catalog в наше пространство имен.
1. В файл "/bitrix/components/ваше название/catalog/component.php" добавляем обработку того что у нас за урл. Заменяем блок отвечающих за обработку ЧПУ (условие if($arParams["SEF_MODE"] == "Y")) нашим собственным:
Код
if($arParams["SEF_MODE"] == "Y")
{
$arVariables = array();
$arUrlTemplates = CComponentEngine::MakeComponentUrlTemplates($arDefaultUrlTemplates404, $arParams["SEF_URL_TEMPLATES"]);
$arVariableAliases = CComponentEngine::MakeComponentVariableAliases($arDefaultVariableAliases404, $arParams["VARIABLE_ALIASES"]);
if (false !== strpos($arUrlTemplates['section'], '#SECTION_PATH#') || false !== strpos($arUrlTemplates['element'], '#SECTION_PATH#')) {
$componentPage = ITCASectionPath::parseComponentPath(
$arParams['IBLOCK_ID'],
$arParams["SEF_FOLDER"],
$arUrlTemplates,
$arVariables
);
} else {
$componentPage = CComponentEngine::ParseComponentPath(
$arParams["SEF_FOLDER"],
$arUrlTemplates,
$arVariables
);
}
$b404 = false;
if(!$componentPage)
{
$componentPage = "sections";
$b404 = true;
}
if(
$componentPage == "section"
&& isset($arVariables["SECTION_ID"])
&& intval($arVariables["SECTION_ID"])."" !== $arVariables["SECTION_ID"]
)
$b404 = true;
if($b404 && $arParams["SET_STATUS_404"]==="Y")
{
$folder404 = str_replace("\\", "/", $arParams["SEF_FOLDER"]);
if ($folder404 != "/")
$folder404 = "/".trim($folder404, "/ \t\n\r\0\x0B")."/";
if (substr($folder404, -1) == "/")
$folder404 .= "index.php";
if($folder404 != $APPLICATION->GetCurPage(true))
CHTTP::SetStatus("404 Not Found");
}
CComponentEngine::InitComponentVariables($componentPage, $arComponentVariables, $arVariableAliases, $arVariables);
$arResult = array(
"FOLDER" => $arParams["SEF_FOLDER"],
"URL_TEMPLATES" => $arUrlTemplates,
"VARIABLES" => $arVariables,
"ALIASES" => $arVariableAliases
);
}
По сути добавляется просто проверка присутствия в правиле нашей переменной #SECTION_PATH# и поручаем провести проверку нашему классу ITCASectionPath. По сути, при положительной проверке мы указываем системе что используем идентификатор раздела вместо переменной пути и отдаем дальнейшее стандартным обработчикам.
И если все сделали правильно мы получаем нормальные ЧПУ урлы для каталога (разделы и итемы), не заморачиваемся с другими компонентами и не грузим систему доп проверками.
Надеюсь это будет кому-то полезно.