Реализация отображения 404 страницы в Битриксе

В Битриксе есть проблема отображения 404 страницы, если на сайте используются компоненты, такие как catalog/news.list и пр. (отображающие какую-либо информацию из инфоблока). Проблема заключается в том, что при запросе какого-либо несуществующего раздела/элемента — производится отображение либо надписи, что раздел/элемент не найден, либо показывается начальная страница каталога. Как же быть?

Есть решение:

В init.php добавляем

AddEventHandler("main", "OnEpilog", "ShowError404");

function ShowError404() {
    if (CHTTP::GetLastStatus()=='404 Not Found') {
        global $APPLICATION;
        $APPLICATION->RestartBuffer();
        require $_SERVER['DOCUMENT_ROOT'].SITE_TEMPLATE_PATH.'/header.php';
        require $_SERVER['DOCUMENT_ROOT'] . '/404.php';
        require $_SERVER['DOCUMENT_ROOT'].SITE_TEMPLATE_PATH.'/footer.php';    
    }
}

Обработчик вызывается в самом конце создания страницы, чтобы точно быть уверенными, что все компоненты вызвались. CHTTP::GetLastStatus() устанавливается в компонентах Битрикса, т.е. как раз тот случай, который не обыгрывается стандартным решением от Битрикса. Далее мы сбрасываем буфер, тем самым очищаем всё, что успело попасть в него до этого. Заключительным шагом мы подключаем напрямую хедер, файл 404 ошибки и футер.

Сама 404 страница может выглядеть как угодно. Например, на неё можно добавить sitemap:

Тут любой ваш текст
<?
$APPLICATION->IncludeComponent("bitrix:main.map", ".default", array(
        "CACHE_TYPE" => "A",
        "CACHE_TIME" => "36000000",
        "SET_TITLE" => "Y",
        "LEVEL" =>      "3",
        "COL_NUM"       =>      "2",
        "SHOW_DESCRIPTION" => "Y"
        ),
        false
);
?>

Другим решением является явный костыль, но всё же его можно взять на заметку.

Идея в следующем: проверить присутствует ли элемент/раздел в инфоблоке товаров с символьным кодом взятым из URL адреса. Если присутствует — показывать страницу товара/раздела, если нет — показывать 404 ошибку:

//получим последний непустой элемент разбивки url по разделителю "/":
$sections = explode('/', $APPLICATION->GetCurPage());
$sections = array_filter(
  $sections,
  function($el){ return !empty($el);}
);
$last_el = end($sections);

$show_404 = true;
$arFilter = Array("IBLOCK_ID"=>50, "ACTIVE"=>"Y", "CODE"=>$last_el);
$arSelect = Array("ID", "NAME");
$res = CIBlockElement::GetList(Array(), $arFilter, false, false, $arSelect);
if($ob = $res->GetNext())
{
	$show_404 = false;
}
$res = CIBlockSection::GetList(Array(), $arFilter, false, $arSelect);
if($ob = $res->GetNext())
{
	$show_404 = false;
}
if ($show_404) echo '404';
if(($show_404)&&(!CSite::InDir("/404.php")))
{
	LocalRedirect("/404.php");
}