"Did You Mean" Suggestions for Empty Searches
When a search finds nothing, suggests a corrected spelling based on the words in your post titles, such as "Did you mean chocolate brownies?", with a link to search again.
Opens WPIDE on
Code
// Text before the suggestion.
$wpide_dym_label = 'Did you mean';
$wpide_dym_vocabulary = function () {
$words = get_transient( 'wpide_dym_words' );
if ( is_array( $words ) ) {
return $words;
}
global $wpdb;
$types = array_values( get_post_types( array( 'exclude_from_search' => false ) ) );
$holder = implode( ',', array_fill( 0, count( $types ), '%s' ) );
$titles = $wpdb->get_col( $wpdb->prepare( "SELECT post_title FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ($holder) ORDER BY post_date DESC LIMIT 3000", $types ) ); // phpcs:ignore WordPress.DB
$words = array();
foreach ( $titles as $title ) {
foreach ( preg_split( '/[^\p{L}\p{N}]+/u', strtolower( $title ), -1, PREG_SPLIT_NO_EMPTY ) as $word ) {
if ( strlen( $word ) > 2 ) {
$words[ $word ] = true;
}
}
}
$words = array_keys( $words );
set_transient( 'wpide_dym_words', $words, DAY_IN_SECONDS );
return $words;
};
add_action( 'save_post', function ( $post_id, $post ) {
if ( 'publish' === $post->post_status ) {
delete_transient( 'wpide_dym_words' );
}
}, 10, 2 );
$wpide_dym_html = function () use ( $wpide_dym_vocabulary, $wpide_dym_label ) {
static $html = null;
if ( null !== $html ) {
return $html;
}
$html = '';
$terms = (array) get_query_var( 'search_terms' );
$vocab = $wpide_dym_vocabulary();
if ( ! $terms || ! $vocab || count( $terms ) > 6 ) {
return $html;
}
$lookup = array_flip( $vocab );
$changed = false;
$fixed = array();
foreach ( $terms as $term ) {
$term = strtolower( $term );
$best = $term;
if ( strlen( $term ) > 3 && ! isset( $lookup[ $term ] ) ) {
// Allow 1 typo in short words and 2 in longer ones.
$limit = strlen( $term ) >= 7 ? 2 : 1;
$score = $limit + 1;
foreach ( $vocab as $word ) {
if ( abs( strlen( $word ) - strlen( $term ) ) > $limit ) {
continue;
}
$distance = levenshtein( $term, $word );
if ( $distance < $score ) {
$score = $distance;
$best = $word;
}
}
}
$changed = $changed || $best !== $term;
$fixed[] = $best;
}
if ( $changed ) {
$suggestion = implode( ' ', $fixed );
$html = '<p class="wpide-did-you-mean">' . esc_html( $wpide_dym_label ) . ' <a href="' . esc_url( get_search_link( $suggestion ) ) . '"><strong>' . esc_html( $suggestion ) . '</strong></a>?</p>';
}
return $html;
};
// Classic themes: printed where the "nothing found" message starts.
add_action( 'loop_no_results', function ( $query ) use ( $wpide_dym_html ) {
static $printed = false;
if ( $printed || wp_is_block_theme() || ! $query->is_main_query() || ! $query->is_search() ) {
return;
}
$printed = true;
echo $wpide_dym_html(); // phpcs:ignore WordPress.Security.EscapeOutput -- escaped when built.
} );
// Block themes: added to the Query block's "No results" area.
add_filter( 'render_block_core/query-no-results', function ( $content ) use ( $wpide_dym_html ) {
return is_search() && '' !== trim( $content ) ? $wpide_dym_html() . $content : $content;
} );
Notes
Works best for Latin-alphabet words; the word list is cached for a day and refreshed when you publish.
How to add this snippet
- Install WPIDE on your WordPress site (it is free) and open Code Snippets.
- Use snippet above adds it from the library in one click. Or copy the code into a new snippet with the same code type.
- It is added inactive: review the code and settings, then switch it on.