Пошук на сайті – річ, яку помічаєш лише тоді, коли він працює погано. Коли я робив свою першу тему, то вставив форму пошуку в шапку, скопіював index.php у search.php і дописав зверху рядок «знайдено N збігів». Працювало, але той рядок робив зайвий запит до бази, а функції в ньому давно прибрали з WordPress. Тож розповім, як сьогодні зробити в темі нормальну форму пошуку і сторінку результатів: з кількістю знайденого, підсвіткою запиту і без зайвих запитів.

Два файли: форма і результати

За пошук у темі відповідають два файли:

  • searchform.php – сама форма: поле і кнопка;
  • search.php – сторінка з результатами.

Обидва необов'язкові. Без searchform.php WordPress виведе свою стандартну форму, а без search.php результати покаже файл index.php, як і будь-яку іншу сторінку, для якої немає окремого шаблону. Але свої файли дають повний контроль над розміткою, тож я роблю їх завжди.

Форма пошуку: searchform.php

<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
    <label>
        <span class="screen-reader-text"><?php esc_html_e( 'Search for:', 'mytheme' ); ?></span>
        <input type="search" class="search-field" name="s"
               value="<?php echo get_search_query(); ?>"
               placeholder="<?php esc_attr_e( 'Search…', 'mytheme' ); ?>">
    </label>
    <button type="submit" class="search-submit"><?php esc_html_e( 'Search', 'mytheme' ); ?></button>
</form>

Що тут важливо:

  • name="s" – саме за параметром s WordPress розуміє, що це пошук (/?s=слово). Колись я писав, що його можна міняти на свій розсуд, – ні, не можна: з іншою назвою пошук просто не спрацює.
  • action веде на головну сторінку сайту, а home_url() підставить правильну адресу навіть після переїзду на інший домен.
  • get_search_query() повертає те, що людина шукала, вже екранованим, тож після пошуку запит лишається в полі і його можна підправити.
  • type="search" замість text – на телефоні з'явиться клавіатура з кнопкою «Пошук», а в полі – хрестик для очищення.
  • label. Підказка в placeholder зникає, щойно починаєш друкувати, і програми для незрячих не завжди її читають. Тому поле загорнуте в <label> з текстом, який видно лише програмам читання (клас screen-reader-text ховає його візуально, стилі для нього є в кожній стандартній темі WordPress).
  • Без id. Форма часто буває на сторінці двічі: у шапці і на сторінці «нічого не знайдено». Однакові id на одній сторінці – помилка, тож лише класи.

Вивести форму можна будь-де одним рядком, наприклад у header.php:

<?php get_search_form(); ?>

Ця функція сама знайде searchform.php у темі. Якщо ж ти не хочеш писати свою форму, а стандартна тебе влаштовує, додай у functions.php підтримку HTML5-розмітки, щоб WordPress видавав сучасну форму:

add_theme_support( 'html5', array( 'search-form' ) );

Сторінка результатів: search.php

<?php get_header(); ?>

<main id="main" class="site-main">
    <?php if ( have_posts() ) : ?>

        <header class="page-header">
            <h1 class="page-title">
                <?php
                printf(
                    esc_html( _n( '%1$s result for “%2$s”', '%1$s results for “%2$s”', $wp_query->found_posts, 'mytheme' ) ),
                    number_format_i18n( $wp_query->found_posts ),
                    get_search_query()
                );
                ?>
            </h1>
        </header>

        <?php while ( have_posts() ) : the_post(); ?>
            <?php get_template_part( 'template-parts/content' ); ?>
        <?php endwhile; ?>

        <?php the_posts_pagination(); ?>

    <?php else : ?>

        <?php get_template_part( 'template-parts/content', 'none' ); ?>

    <?php endif; ?>
</main>

<?php get_footer(); ?>

Будова та сама, що в index.php і в шаблоні категорії, відрізняється лише заголовок.

Скільки знайдено

Старий код рахував результати окремим запитом, який вибирав з бази всі знайдені записи без обмеження. На великому сайті це повільно, і головне – зайве: WordPress уже порахував їх, щоб зробити посторінкову навігацію. Кількість лежить у $wp_query->found_posts, лишається тільки вивести.

Функція _n() вибирає правильну форму слова залежно від числа. В англійській форм дві (result, results), а в українській три: «1 результат», «2 результати», «5 результатів». Усі три вписуються у файл перекладу теми, і WordPress сам підставить потрібну, – як це налаштувати, читай у статті як підготувати шаблон WordPress для перекладу. А number_format_i18n() покаже велике число з пробілом між тисячами, як прийнято в українській.

Нічого не знайдено

Якщо результатів немає, підключається template-parts/content-none.php, який ми зробили разом з index.php. Він показує повідомлення і ще раз форму пошуку, щоб людина могла одразу спробувати інший запит, а не тиснула «Назад».

Підсвітка запиту в результатах

Приємна дрібниця: виділити шукане слово в назвах і уривках, щоб одразу було видно, чому знайшовся саме цей запис. Додаємо у functions.php:

function mytheme_highlight_search( $text ) {
    if ( ! is_search() || ! in_the_loop() ) {
        return $text;
    }

    $words = preg_split( '/\s+/u', get_search_query( false ), -1, PREG_SPLIT_NO_EMPTY );

    foreach ( $words as $word ) {
        if ( mb_strlen( $word ) < 3 ) {
            continue;
        }
        $text = preg_replace( '/(' . preg_quote( $word, '/' ) . ')(?![^<]*>)/iu', '<mark>$1</mark>', $text );
    }

    return $text;
}

add_filter( 'the_title', 'mytheme_highlight_search' );
add_filter( 'the_excerpt', 'mytheme_highlight_search' );

Що тут відбувається:

  • Функція працює лише на сторінці пошуку і лише всередині циклу, щоб не зачепити меню чи назви записів у бічній колонці.
  • Запит розбивається на слова, а занадто короткі (до 3 літер) пропускаються – інакше прийменник «на» підсвітився б у половині тексту.
  • Кожне слово загортається в тег <mark> без урахування регістру. Шматок (?![^<]*>) не дає замінити текст усередині HTML-тегів, щоб випадково не зламати розмітку.

Браузер за замовчуванням малює <mark> жовтим фоном, але його легко підлаштувати під свій дизайн:

.search-results mark {
    background: #fff3b0;
    color: inherit;
    padding: 0 0.1em;
}

Клас search-results сам додається до <body> на сторінці пошуку, тож стиль не вплине на інші <mark> на сайті.

Що саме шукати

За замовчуванням WordPress шукає і в записах, і в сторінках. Якщо сторінки на кшталт «Контакти» чи «Політика конфіденційності» тобі в результатах не потрібні, обмеж пошук лише записами:

function mytheme_search_only_posts( $query ) {
    if ( ! is_admin() && $query->is_main_query() && $query->is_search() ) {
        $query->set( 'post_type', 'post' );
    }
}

add_action( 'pre_get_posts', 'mytheme_search_only_posts' );

Хук pre_get_posts змінює запит ще до того, як WordPress піде в базу. Так само можна змінити кількість результатів на сторінці чи їхній порядок – детальніше про цей хук я писав у статті правильне сортування статей у WordPress.

І чесно про обмеження. Вбудований пошук просто шукає збіг тексту в назві, уривку і вмісті, ставлячи вище записи, у назві яких є запит. Відмінків він не розуміє: на запит «пісня» не знайде «пісні». Для невеликого блогу цього вистачає, а якщо сайт великий і пошук для нього важливий, тоді вже варто дивитися в бік спеціальних плаґінів.

Що далі

Залишився останній обов'язковий шматок класичної теми – низ сторінки: ось як зробити файл footer.php. А якщо тему ти робиш з нуля і ще не бачив/ла всієї картини, почни зі статті як створити тему WordPress з нуля.

Більше про це – за позначками Створення тем WordPress та PHP.

А на твоєму сайті пошуком хтось узагалі користується? Пиши в коментарях 🙂