Sissejuhatus
Otsing on iga sisuga lehe vajalik osa. Vaikimisi WordPress’i otsing töötab (URL-il ?s=keyword), aga vaikimisi tulemustelehel on kolm puudust: pole dedikeeritud template’i, excerpt’is võib otsingusõna puududa, ja otsingusõna pole esile tõstetud.
Selles postituses ehitame otsingulehe (templates/search.html), mis kuvab pealkirjad ja kontekstipõhise snippet’i koos bold-itud otsingusõnaga. UTF-8 turvaline (eesti tähed Ä, Õ, Ü, Ö töötavad).
Postitus on kirjutatud nii, et õpilane saab seda samm-sammult järgi teha. Tähelepanu: see ülesanne paljastab WP filtrite kihti, mis on FSE-arendaja initsiatsiooni-rituaal. Soovitan lugeda lõpuni.
Eesmärk
Frontend’i tulemus, mida tahame:
- Hero ülaosas: “Search results for: ‘otsingusõna’”
- Otsinguvorm uuesti otsida
- Tulemused: igaüks H2 pealkiri + excerpt-snippet
- Pealkirjas ja snippet’is on otsingusõna paksult (bold)
- Snippet näitab konteksti (~80 tähemärki enne ja pärast), mitte vaikimisi esimesi 55 sõna
- “No results” sõnum, kui midagi ei leita
- Eesti tähed töötavad otsingus
Eeldused
- WordPress 6.x (Block Editor)
- FSE block-teema (templates/ kaust olemas)
- Vähemalt mõned postitused, mille sisus on otsitav sõna
- DDEV või muu kohalik keskkond
Samm 1: search.html template
Miks?
WP template hierarchy järgi: kui URL sisaldab ?s=keyword, WP otsib search.html template’i. Kui pole, kasutab index.html fallback’ina. index.html aga ei tea otsingu kontekstist midagi – see näitab postitusi nagu peavoolus.
Dedikeeritud search.html annab:
- Selge “Search results” pealkirja (`wp:query-title` blokiga)
- Võimaluse uuesti otsida (`wp:search` blokk)
- “No results” tagavara, kui midagi ei leita (`wp:query-no-results` blokk)
- Kontrolli tulemuste-kaartide üle
Kuidas?
Loo fail wp-content/themes/[teema]/templates/search.html:
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
<!-- Hero: Search results title -->
<!-- wp:group {"tagName":"section","align":"full","backgroundColor":"bg-gray",...} -->
<section>
<!-- wp:query-title {"type":"search","textAlign":"center","level":1,"showPrefix":false} /-->
<!-- wp:paragraph {"align":"center"} -->
<p>Allpool postitused ja lehed, mis sinu otsingule vastavad.</p>
<!-- /wp:paragraph -->
<!-- wp:search {"placeholder":"Otsi uuesti…","buttonText":"Otsi"} /-->
</section>
<!-- /wp:group -->
<!-- Query Loop with inherit:true loeb URL'ist ?s=keyword -->
<!-- wp:query {"query":{"perPage":10,"inherit":true,...},"align":"wide"} -->
<div class="wp-block-query alignwide">
<!-- wp:post-template -->
<!-- wp:group {"tagName":"article",...} -->
<article>
<!-- wp:post-title {"level":2,"isLink":true,"fontSize":"large"} /-->
<!-- wp:post-excerpt {"moreText":"Vaata edasi","showMoreOnNewLine":true} /-->
</article>
<!-- /wp:group -->
<!-- /wp:post-template -->
<!-- wp:query-pagination -->...<!-- /wp:query-pagination -->
<!-- wp:query-no-results -->
<!-- wp:paragraph {"align":"center"} -->
<p>Sellele otsingule midagi ei leitud. Proovi mõnd teist sõna.</p>
<!-- /wp:paragraph -->
<!-- /wp:query-no-results -->
</div>
<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
Olulisemad blokid
wp:query-title type="search"– loeb URL-ist?sväärtuse ja kuvab “Search results for: ‘keyword’”wp:search– otsinguvorm. Asukoht hero sees, et kasutaja saaks uuesti otsidawp:query inherit:true– Query Loop pärib otsingu-päringut URL’ist, ei vaja omapostTypewp:query-no-results– kuvatakse, kui päring tagastab tühja
Mida kontrollida?
Salvesta fail. Ava brauser: https://[sait]/?s=test (asenda “test” mingi sõnaga, mis on sinu postitustes). Peaksid nägema struktuuri (hero, otsinguvorm, tulemused), aga otsingusõna POLE veel paksult – seda lisame järgmistes sammudes.
Samm 2: Kontekstipõhine excerpt
Miks?
WordPress’i vaikimisi excerpt on esimesed 55 sõna sisust. Aga kui otsingusõna on lõigus 200, näeb kasutaja excerpt’is hoopis teist teksti – ei saa aru, miks see tulemus kuvatakse.
Lahendus: filter, mis OTSINGU TULEMUSTE puhul leiab otsingusõna asukoha sisus ja kuvab ~80 tähemärki enne ja pärast. Annab kohese konteksti.
Kuidas?
Ava functions.php. Lisa:
/**
* Search: kontekstipõhine excerpt.
*/
function fse_theme_search_contextual_excerpt( $excerpt, $post ) {
if ( ! is_search() || ! $post instanceof WP_Post ) return $excerpt;
$query = get_search_query();
if ( empty( $query ) ) return $excerpt;
$content = wp_strip_all_tags( $post->post_content );
if ( empty( $content ) ) return $excerpt;
$pos = mb_stripos( $content, $query );
if ( $pos === false ) return $excerpt;
$start = max( 0, $pos - 80 );
$length = mb_strlen( $query ) + 160;
$snippet = mb_substr( $content, $start, $length );
if ( $start > 0 ) $snippet = '… ' . $snippet;
if ( $start + $length < mb_strlen( $content ) ) $snippet .= ' …';
return $snippet;
}
add_filter( 'get_the_excerpt', 'fse_theme_search_contextual_excerpt', 10, 2 );
Olulised detailid
is_search()– ainult otsingulehel (mujal vaikimisi excerpt)$post instanceof WP_Post– kaitse, kui filter saab veidra objektimb_stripos,mb_substr,mb_strlen– UTF-8 turvaline (eesti tähed!)- Kui otsingusõna pole sisus (võib olla pealkirjas), tagastame vaikimisi excerpt’i
- Ellipsis (… ja …) annavad lugejale teada, et see on katkend
Mida kontrollida?
Tee hard refresh otsingulehel. Vaata excerpt’i tulemustes – kas see on nüüd otsingusõna ümbrusest, mitte vaikimisi esimesed 55 sõna? Veendu, et hard refresh oli (Cmd+Shift+R Mac, Ctrl+Shift+R muu), sest WP cache’ib agressiivselt.
Samm 3: Bold pealkirjas
Miks?
Tulemuste skannitavus paraneb dramaatiliselt, kui otsingusõna on tulemustes esile tõstetud. Kasutaja näeb kohe, miks see konkreetne postitus oli kuvatud.
Kuidas?
Lisa functions.php-sse:
/**
* Search: bold otsingusõna pealkirjas.
*/
function fse_theme_search_highlight_title( $title ) {
if ( ! is_search() || ! in_the_loop() ) return $title;
$query = get_search_query();
if ( empty( $query ) ) return $title;
return preg_replace(
'/(' . preg_quote( $query, '/' ) . ')/iu',
'<strong>$1</strong>',
$title
);
}
add_filter( 'the_title', 'fse_theme_search_highlight_title' );
Detailid
in_the_loop()– kaitse, et navi-menüü, header’i ja muude kohtade pealkirjades ei bold’i. Ainult Query Loop’i seespreg_quote– escape regex erimärke kasutaja sisendis (kui ta otsib “v.*”, ei taha me regex’i tõlgendust)/uflag – UTF-8 mode, vajalik eesti tähtede (Ä, Õ, Ü, Ö) jaoks/iflag – case-insensitive, et “Vundamendi” ja “vundamendi” mõlemad leiaks
Mida kontrollida?
Hard refresh. Vaata otsingulehel pealkirju – kas otsingusõna on neis paksult? Proovi otsinguid eri sõnaga, sh suure ja väikse algustähega.
Samm 4: Bold excerpt’s
NB! See samm on keerulisem kui eelnevad. Eelmistel Samm 1–3 oli muster: Miks → Kuidas → Mida kontrollida. Selles sammus on lisaks kolmas kiht: enne õiget lahendust näeme kaks loomulikku katset, mis ei tööta. See on tahtlik — tutvustab WP filtrite kihtide ehitust. Loe hoolikalt ja ära kiirusta.
Loomulik (aga vale) lähenemine
Sa võiksid arvata: “Eelmine filter töötas pealkirjale (the_title), nüüd lisan sama excerpt’ile (the_excerpt või get_the_excerpt).” Loogika ütleb seda.
Aga see EI TÖÖTA. Miks?
Kaks lõksu järjest
Lõks #1: wp:post-excerpt blokk EI käivita the_excerpt filtrit. Blokk-renderdamise kood kasutab otse get_the_excerpt() funktsiooni. Niisiis kõik the_excerpt filtrid on FSE block-themes-is tähelepanuta.
Lõks #2: Hea, niisiis kasutame get_the_excerpt filtrit. Filter käivitub korralikult ja tagastab tagasi <strong>tekst</strong>. AGA pärast filtrit kutsub blokk wp_trim_words(), mis kasutab seest wp_strip_all_tags() – kogu HTML kaob.
Tagajärg: <strong> tag, mille filter sisestas, lihtsalt strip’itakse ära enne renderdamist. Frontend näeb plaint teksti.
Õige lahendus: render_block_core/post-excerpt
FSE blokkide puhul on kõige hilisem filter ehk see, mis käivitub kõige hilisemana, kõige ohutum koht muudatuste tegemiseks. Iga blokk on render_block_core/{block-name} filtri all – see käivitub pärast kogu blokk-renderdamist.
Lisa functions.php-sse:
/**
* Search: bold otsingusõna wp:post-excerpt bloki HTML-is.
*
* Käivitub PÄRAST wp_trim_words() HTML-stripimist.
* See on ainus koht, kus <strong> säilib.
*/
function fse_theme_search_highlight_excerpt_block( $block_content, $block ) {
if ( ! is_search() ) return $block_content;
$query = get_search_query();
if ( empty( $query ) ) return $block_content;
return preg_replace(
'/(' . preg_quote( $query, '/' ) . ')/iu',
'<strong>$1</strong>',
$block_content
);
}
add_filter( 'render_block_core/post-excerpt', 'fse_theme_search_highlight_excerpt_block', 10, 2 );
Mida kontrollida?
Hard refresh. Vaata frontend’s – otsingusõna peaks nüüd olema paks ka excerpt’s. Kui ei – kontrolli source’i (View Page Source), kas <strong> on HTML-is.
Terve functions.php pärast seda sammu (versioon 4 – LÕPLIK)
Lõpuks näeb functions.php välja nii. Muudatused võrreldes versiooniga 3 (#7 lõpus): kolm uut filter-funktsiooni otsingu-tulemuste lehe jaoks:
<?php
/**
* fse-theme functions.php
*
* Theme support + block styles + pattern categories + page excerpt UI +
* meta-väljade registreerimine + otsingu-filtrid.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Theme support deklaratsioonid.
*/
function fse_theme_setup() {
add_theme_support( 'post-thumbnails' );
add_theme_support( 'html5', [
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
'style',
'script',
] );
add_theme_support( 'wp-block-styles' );
add_post_type_support( 'page', 'excerpt' );
}
add_action( 'after_setup_theme', 'fse_theme_setup' );
/**
* Block style: Secondary Button.
*/
function fse_theme_register_block_styles() {
register_block_style( 'core/button', [
'name' => 'secondary',
'label' => __( 'Secondary', 'fse-theme' ),
] );
}
add_action( 'init', 'fse_theme_register_block_styles' );
/**
* Register ACF meta fields for REST API + Block Bindings.
*/
function fse_theme_register_meta_fields() {
register_post_meta( 'service', 'price_from', [
'type' => 'number',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'service', 'icon', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'service', 'featured', [
'type' => 'boolean',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'job_title', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'email', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
register_post_meta( 'team', 'linkedin', [
'type' => 'string',
'single' => true,
'show_in_rest' => true,
] );
}
add_action( 'init', 'fse_theme_register_meta_fields' );
/**
* UUS: Search – kontekstipõhine excerpt.
*/
function fse_theme_search_contextual_excerpt( $excerpt, $post ) {
if ( ! is_search() || ! $post instanceof WP_Post ) {
return $excerpt;
}
$query = get_search_query();
if ( empty( $query ) ) {
return $excerpt;
}
$content = wp_strip_all_tags( $post->post_content );
if ( empty( $content ) ) {
return $excerpt;
}
$pos = mb_stripos( $content, $query );
if ( $pos === false ) {
return $excerpt;
}
$start = max( 0, $pos - 80 );
$length = mb_strlen( $query ) + 160;
$snippet = mb_substr( $content, $start, $length );
if ( $start > 0 ) {
$snippet = '… ' . $snippet;
}
if ( $start + $length < mb_strlen( $content ) ) {
$snippet .= ' …';
}
return $snippet;
}
add_filter( 'get_the_excerpt', 'fse_theme_search_contextual_excerpt', 10, 2 );
/**
* UUS: Search – bold otsingusõna excerpt-bloki HTML-is.
*/
function fse_theme_search_highlight_excerpt_block( $block_content, $block ) {
if ( ! is_search() ) {
return $block_content;
}
$query = get_search_query();
if ( empty( $query ) ) {
return $block_content;
}
return preg_replace(
'/(' . preg_quote( $query, '/' ) . ')/iu',
'<strong>$1</strong>',
$block_content
);
}
add_filter( 'render_block_core/post-excerpt', 'fse_theme_search_highlight_excerpt_block', 10, 2 );
/**
* UUS: Search – bold otsingusõna pealkirjas.
*/
function fse_theme_search_highlight_title( $title ) {
if ( ! is_search() || ! in_the_loop() ) {
return $title;
}
$query = get_search_query();
if ( empty( $query ) ) {
return $title;
}
return preg_replace(
'/(' . preg_quote( $query, '/' ) . ')/iu',
'<strong>$1</strong>',
$title
);
}
add_filter( 'the_title', 'fse_theme_search_highlight_title' );
/**
* Pattern kategooriad.
*/
function fse_theme_register_pattern_categories() {
register_block_pattern_category( 'fse-theme-hero', [
'label' => __( 'FSE Theme – Hero', 'fse-theme' ),
] );
register_block_pattern_category( 'fse-theme-card', [
'label' => __( 'FSE Theme – Cards', 'fse-theme' ),
] );
register_block_pattern_category( 'fse-theme-section', [
'label' => __( 'FSE Theme – Sections', 'fse-theme' ),
] );
}
add_action( 'init', 'fse_theme_register_pattern_categories' );
See on kogu functions.php evolutsiooni lõpp-punkt projekti praegusele seisule. Edaspidi (nt avalehe Featured Services, single-service.html), võib lisada veel funktsioone, aga need 4 versiooni katavad kõik tähtsamad õppetunnid.
Reegel filtri valimisel FSE blokkidega
See on suur õppetund kogu projektist. Tee selge reegel oma peas:
| Tahad muuta… | Kasuta filtrit |
|---|---|
| Sisu enne renderdamist | get_the_content, get_the_excerpt, the_title |
| Renderdatud HTML-i (incl. inline HTML säilumist) | render_block_core/{block-name} |
| Excerpt’i HTML (sealhulgas pärast wp_trim_words) | render_block_core/post-excerpt |
| Vana classic style template | the_excerpt (FSE-s ei käivitu) |
Õpilastele: kui sa lisad HTML-i excerpt-blokis ja see kaob, vahetage filter get_the_excerpt → render_block_core/post-excerpt. See on FSE-spetsiifiline lõks, mida vanades teemades polnud.
UTF-8 turvalisus (eesti tähed)
Kuna projekt on eesti keeles, on kohustuslik kasutada UTF-8 turvalisi funktsioone:
mb_strlen(mittestrlen) – pikkus tähemärkides, mitte baitidesmb_stripos(mittestripos) – case-insensitive otsingmb_substr(mittesubstr) – substring tähemärkides- Regex’is
/uflag – UTF-8 mode
Ilma nendeta: kui otsid “Õppimine”, saad katki snippet’i (ä, õ, ü on 2-baidised) ja regex käitub valesti.
Veaotsing
Excerpt näitab vaikimisi (mitte snippet)
- Kontrolli, kas otsingusõna ON sisus (mitte ainult pealkirjas)
ddev exec wp cache flush- Hard refresh brauseris
- Kontrolli, kas filter on registreeritud:
get_the_excerpt(mittethe_excerpt)
Bold pole excerpt’s näha (aga pealkirjas on)
- Lõks #2 – wp_trim_words strip’b HTML
- Lisa
render_block_core/post-excerptfilter (Samm 4)
Eesti tähed katki (“Õppimine” annab segase tulemuse)
- Asenda
strlen,stripos,substr→mb_strlen,mb_stripos,mb_substr - Lisa
/uflag regex’i - Kontrolli, kas mbstring laiend on PHP-s aktiivne:
ddev exec php -m | grep mbstring
Õppetundide kokkuvõte
search.htmlon WP standardne template’i nimi – tee see kaustatemplates/wp:query-title type="search"kuvab “Search results for: ‘query’”wp:query inherit:truepärib otsingu-päringu URL’ist – ei vaja omapostTypewp:query-no-resultsblokk käsitleb “midagi ei leitud” elegantselt- Kontekstipõhine snippet annab kasutajale konteksti, miks tulemus on relevantne
- FSE-s ei kasuta
the_excerpt, kasutaget_the_excerpt - HTML-i lisamine excerpt’i: ainult
render_block_core/post-excerptfilter säilib - UTF-8 turvalisus on eesti keelse projekti puhul kohustuslik
Mis tuleb järgmiseks
Otsingulehe ehitamine paljastas kihti, mis on FSE-arendaja igapäev. Sama muster (sisu-filter vs blokk-renderdamise filter) tuleb tagasi alati, kui sa tahad lisada midagi blokkidesse, mida WP vaikimisi ei luba.
Järgmised loomulikud sammud projektis:
page.htmltemplate (Meist, Kontakt, Privaatsuspoliitika jaoks – PageHero pattern reference’iga)single-service.htmltemplate (üksiku teenuse leht ServiceHero + Body + FAQ)- Avaleht Featured Services (Query Loop filtriga
meta_query: featured=true) - Otsinguvormi lisamine header’isse, et oleks alati kättesaadav
Kõik need õppetunnid on detailselt talletatud project/casestudy.md-s. Soovituslik lugemise järjekord:
- #0 (Mida me teeme)
- #1, #2, #3 (theme.json, plaan, vundament)
- #4 (FSE filosoofia)
- #5 (PageHero)
- #6 (Blog Home)
- #7 (Service archive, Block Bindings)
- #8 (see postitus, otsinguleht ja filtrid)
Hea õppimist.