Skip to content

Instantly share code, notes, and snippets.

@andrewinsidelazarev
Last active February 6, 2026 12:03
Show Gist options
  • Select an option

  • Save andrewinsidelazarev/a154843e447333fd2247585d74c9cea3 to your computer and use it in GitHub Desktop.

Select an option

Save andrewinsidelazarev/a154843e447333fd2247585d74c9cea3 to your computer and use it in GitHub Desktop.
Advanced 404-page snippet for WordPress
function handle_custom_404() {
// Получаем запрашиваемый путь URL
$request_uri = $_SERVER['REQUEST_URI'];
$request_path = trim(urldecode($request_uri), '/');
// Извлекаем только последний сегмент пути
$last_segment = basename($request_path);
// Функция для поиска существующего пути в других папках
function find_existing_path($path) {
global $wpdb;
// Выполняем объединенный запрос для поиска слага в постах и тегах без ограничения LIMIT 1
$results = $wpdb->get_results($wpdb->prepare(
"(SELECT ID AS item_id, 'post' AS item_type FROM {$wpdb->posts}
WHERE post_name = %s AND post_status = 'publish')
UNION ALL
(SELECT t.term_id AS item_id, 'tag' AS item_type FROM {$wpdb->terms} t
INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
WHERE t.slug = %s AND tt.taxonomy = 'post_tag')",
$path, $path
));
// Если найдена ровно одна запись, возвращаем её URL
if (count($results) === 1) {
$result = $results[0];
if ($result->item_type === 'post') {
return get_permalink($result->item_id);
} elseif ($result->item_type === 'tag') {
return get_tag_link($result->item_id);
}
}
// Если результатов несколько или ничего не найдено, используем старую логику
$paths_to_check = ['blog', 'category', 'news', 'tag', 'news/news-photo'];
foreach ($paths_to_check as $folder) {
$potential_path = $folder . '/' . $path;
// Проверяем, существует ли страница по этому пути или термин в нужной таксономии
if (url_to_postid(site_url($potential_path)) != 0 || term_exists($path, $folder)) {
return site_url($potential_path);
}
}
return false;
}
// Проверка, существует ли похожий путь в других папках
$existing_path = find_existing_path($last_segment);
if ($existing_path) {
// Если найден похожий путь, выполняем редирект
wp_redirect($existing_path, 301);
exit;
}
// Поиск по ключевым словам, заменяя "-" и "_" на плюсы
$search_query = str_replace(['-', '_', '%20'], '+', $last_segment);
// Выполняем поиск по ключевым словам
$search = new WP_Query([
's' => $search_query,
'posts_per_page' => 5
]);
// Выводим результаты, если они найдены
if ($search->have_posts()) {
echo '</br>';
$locale = get_locale();
if ($locale === 'ru_RU') {
echo '<h2>Возможно, Вы искали это:</h2>';
} elseif ($locale === 'it_IT') {
echo '<h2>Forse stavi cercando questo:</h2>';
} else {
echo '<h2>Perhaps you were looking for this:</h2>';
}
echo '</br>';
echo '<div>';
// Вместо вывода ссылок, выводим миниатюры постов
while ($search->have_posts()) :
$search->the_post();
echo '<div class="post-thumbnail">';
get_template_part('content', get_post_format());
echo '</div>';
endwhile;
// Выводим пагинацию
// the_posts_pagination();
echo '</div>';
wp_reset_postdata();
}
}
@andrewinsidelazarev
Copy link
Author

В файл 404.php в теме вставить:

@andrewinsidelazarev
Copy link
Author

<?php // Вызов функции для проверки и вывода предложений handle_custom_404(); ?>

@andrewinsidelazarev
Copy link
Author

function handle_custom_404() {
// 1. ПОЛУЧАЕМ И ЧИСТИМ URI
// urldecode превращает %2F -> / и %3F -> ?
$request_uri = urldecode($_SERVER['REQUEST_URI']);

// Отрезаем GET-параметры (все что после ?)
$clean_uri = strtok($request_uri, '?');

// Отрезаем слеши и получаем только "хвост" (слаг)
$request_path = trim($clean_uri, '/');
$last_segment = basename($request_path);

// Если мы на главной или путь пустой — выходим
if (empty($last_segment)) return;

// 2. ФУНКЦИЯ ПОИСКА (Безопасное объявление)
if (!function_exists('find_existing_path_logic')) {
    function find_existing_path_logic($path) {
        global $wpdb;

        // Поиск в базе (Посты, Страницы или Теги)
        $results = $wpdb->get_results($wpdb->prepare(
            "(SELECT ID AS item_id, 'post' AS item_type FROM {$wpdb->posts} 
              WHERE post_name = %s AND post_status = 'publish' AND post_type NOT IN ('revision', 'attachment'))
            UNION ALL
            (SELECT t.term_id AS item_id, 'tag' AS item_type FROM {$wpdb->terms} t
              INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
              WHERE t.slug = %s AND tt.taxonomy = 'post_tag')",
            $path, $path
        ));

        if (count($results) === 1) {
            $result = $results[0];
            return ($result->item_type === 'post') ? get_permalink($result->item_id) : get_tag_link($result->item_id);
        }

        // Проверка жестко заданных папок
        $paths_to_check = ['blog', 'category', 'news', 'tag', 'news/news-photo'];
        foreach ($paths_to_check as $folder) {
            $potential_url = home_url($folder . '/' . $path);
            // Проверяем, существует ли пост по этому адресу
            if (url_to_postid($potential_url) != 0 || term_exists($path, $folder)) {
                return $potential_url;
            }
        }
        return false;
    }
}

// 3. РЕДИРЕКТ
$existing_path = find_existing_path_logic($last_segment);

if ($existing_path) {
    // Проверяем, чтобы не редиректить на тот же самый URL (защита от петли)
    $current_url = home_url(add_query_arg([], $GLOBALS['wp']->request));
    if (untrailingslashit($existing_path) !== untrailingslashit($current_url)) {
        wp_redirect($existing_path, 301);
        exit;
    }
}

// 4. ВЫВОД ПОИСКОВОЙ ВЫДАЧИ (Если редирект не удался)
$search_query = str_replace(['-', '_', '%20'], ' ', $last_segment);
$search = new WP_Query([
    's'              => $search_query,
    'posts_per_page' => 5,
    'post_status'    => 'publish'
]);

if ($search->have_posts()) {
    $locale = get_locale();
    $titles = [
        'ru_RU' => 'Возможно, Вы искали это:',
        'it_IT' => 'Forse stavi cercando questo:',
        'default' => 'Perhaps you were looking for this:'
    ];
    $title = $titles[$locale] ?? $titles['default'];

    echo '<div class="custom-404-suggestions">';
    echo '<h2>' . esc_html($title) . '</h2><br/>';
    
    while ($search->have_posts()) : $search->the_post();
        echo '<div class="post-suggestion-item">';
        if (locate_template('content.php')) {
            get_template_part('content', get_post_format());
        } else {
            echo '<h3><a href="' . get_permalink() . '">' . get_the_title() . '</a></h3>';
        }
        echo '</div>';
    endwhile;
    echo '</div>';
    
    wp_reset_postdata();
}

}

@andrewinsidelazarev
Copy link
Author

Главные изменения в финальной версии:
Защита от циклического редиректа: Добавлено сравнение $existing_path с текущим URL. Если они совпадут (например, из-за настроек плагинов), сайт не уйдет в бесконечную перезагрузку.

Исключение вложений: В SQL-запрос добавлено post_type NOT IN ('revision', 'attachment'). Чтобы при поиске по слагу «apple» сайт не редиректил случайно на картинку apple.jpg вместо страницы.

Безопасный вывод: Добавлен esc_html() для заголовков — это базовое правило безопасности WordPress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment