Usage
nebula()->rest_autocomplete_search()
Parameters
term
(Required) (String) Search keyword or phrase
Default: None
Parameter Notes
term is passed in an AJAX data object.
Examples
Globally exclude attachments and menus from autocomplete search results.
add_filter('nebula_autocomplete_ignore_types', function($ignore_post_types){ $ignore_post_types[] = 'attachment'; $ignore_post_types[] = 'menu'; return $ignore_post_types; });
Globally ignore individual posts from autocomplete search results.
add_filter('nebula_autocomplete_ignore_ids', function($ignore_post_ids){ $ignore_post_ids[] = 123; $ignore_post_ids[] = 456; return $ignore_post_ids; });
Additional Notes
This functions searches post data as well as custom field data.
Post types can specifically be excluded from autocomplete searches with the filter nebula_autocomplete_ignore_types
.
Individual posts can specifically be excluded from autocomplete searches with the filter nebula_autocomplete_ignore_ids
.
See examples.
Also, installing/activating Relevanssi will bypass the manual query logic (and therefore the need to ignore post types).
Source File
Located in /libs/Functions.php on line 2561.
3 Hooks
Find these filters and actions in the source code below to hook into them. Use do_action() and add_filter() in your functions file or plugin.
Filters
"nebula_autocomplete_ignore_ids""nebula_autocomplete_ignore_types"
"nebula_autocomplete_ignore_ids"
Need a new filter hook? Request one here.
Actions
This function has no action hooks available. Request one?Note: This function contains 6 to-do comments.
public function rest_autocomplete_search(){ $timer_name = $this->timer('Autocomplete Search'); if ( isset($this->super->get['term']) ){ ini_set('memory_limit', '256M'); //@todo Nebula 0: Remove these when possible... $term = sanitize_text_field(trim($this->super->get['term'])); if ( empty($term) ){ return false; } $types = 'any'; if ( isset($this->super->get['types']) ){ $types = explode(',', sanitize_text_field(trim($this->super->get['types']))); //This is not necessary $types = array_filter($types, function($type){ return $type !== 'nebula_cf7_submits'; }); } //Prepare the standard WP search query parameters (do not include custom fields here). //This is used by both Relevanssi -or- the combined query later $initial_query_args = array( 'post_type' => $types, 'post_status' => 'publish', 'posts_per_page' => 10, //Do not use numberposts or any other parameter instead here 's' => $term, ); if ( function_exists('relevanssi_do_query') ){ //If the Relevanssi plugin is active use its engine $should_be_sorted = false; //Let Relevanssi determine result order $relevanssi_query_prep = new WP_Query(); $relevanssi_query_prep->parse_query($initial_query_args); //Must be performed this way to retrieve Relevanssi results $relevanssi_autocomplete = relevanssi_do_query($relevanssi_query_prep); //Run the query foreach ( $relevanssi_autocomplete as $post ){ $ignore_post_ids = apply_filters('nebula_autocomplete_ignore_ids', array()); //Allow individual posts to be globally ignored from autocomplete search if ( in_array($post->ID, $ignore_post_ids) || !get_the_title($post->ID) ){ //Ignore results without titles continue; } $suggestion = array(); similar_text(strtolower($term), strtolower(esc_html(get_the_title($post->ID))), $suggestion['similarity']); //Determine how similar the query is to this post title $suggestion['label'] = esc_html(get_the_title($post->ID)); $suggestion['link'] = get_permalink($post->ID); $suggestion['classes'] = 'type-' . get_post_type($post->ID) . ' id-' . $post->ID . ' slug-' . $post->post_name . ' similarity-' . str_replace('.', '_', number_format($suggestion['similarity'], 2)); if ( $post->ID == get_option('page_on_front') ){ $suggestion['classes'] .= ' page-home'; } elseif ( is_sticky($post->ID) ){ //@TODO "Nebula" 0: If sticky post. is_sticky() does not work here? $suggestion['classes'] .= ' sticky-post'; } $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); $suggestions[] = $suggestion; } } else { //Manually find relevant posts $query1 = new WP_Query($initial_query_args); //Run the first query with the initial arguments now //Search custom fields now too $query2 = new WP_Query(array( 'post_type' => $types, 'post_status' => 'publish', 'posts_per_page' => 10, //Do not use numberposts or any other parameter instead here 'meta_query' => array( array( 'value' => $term, 'compare' => 'LIKE' ) ) )); //Combine the above queries $autocomplete_query = new WP_Query(); $autocomplete_query->posts = array_unique(array_merge($query1->posts, $query2->posts), SORT_REGULAR); //Is this the right way to do it? Or should we use parse_query here? $autocomplete_query->post_count = count($autocomplete_query->posts); $ignore_post_types = apply_filters('nebula_autocomplete_ignore_types', array('nebula_cf7_submits')); //Allow post types to be globally ignored from autocomplete search $ignore_post_ids = apply_filters('nebula_autocomplete_ignore_ids', array()); //Allow individual posts to be globally ignored from autocomplete search $suggestions = array(); $should_be_sorted = true; //Re-sort results by our similarity score //Loop through the posts if ( $autocomplete_query->have_posts() ){ while ( $autocomplete_query->have_posts() ){ $autocomplete_query->the_post(); if ( in_array(get_the_id(), $ignore_post_ids) || !get_the_title() ){ //Ignore results without titles continue; } $post = get_post(); $suggestion = array(); similar_text(strtolower($term), strtolower(esc_html(get_the_title())), $suggestion['similarity']); //Determine how similar the query is to this post title $suggestion['label'] = esc_html(get_the_title()); $suggestion['link'] = get_permalink(); $suggestion['classes'] = 'type-' . get_post_type() . ' id-' . get_the_id() . ' slug-' . $post->post_name . ' similarity-' . str_replace('.', '_', number_format($suggestion['similarity'], 2)); if ( get_the_id() == get_option('page_on_front') ){ $suggestion['classes'] .= ' page-home'; } elseif ( is_sticky() ){ //@TODO "Nebula" 0: If sticky post. is_sticky() does not work here? $suggestion['classes'] .= ' sticky-post'; } $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); $suggestions[] = $suggestion; } } //Find media library items if ( !$this->in_array_any(array('attachment'), $ignore_post_types) && $this->in_array_any(array('any', 'attachment'), $types) ){ $attachments = get_posts(array('post_type' => 'attachment', 's' => $term, 'numberposts' => 10, 'post_status' => null)); if ( $attachments ){ $attachment_count = 0; foreach ( $attachments as $attachment ){ if ( in_array($attachment->ID, $ignore_post_ids) || strpos(get_attachment_link($attachment->ID), '?attachment_id=') ){ //Skip if media item is not associated with a post. //@todo "Nebula" 0: Update strpos() to str_contains() in PHP8 continue; } $suggestion = array(); $attachment_meta = wp_get_attachment_metadata($attachment->ID); if ( isset($attachment_meta['file']) ){ $path_parts = pathinfo($attachment_meta['file']); $attachment_search_meta = ( get_the_title($attachment->ID) != '' )? get_the_title($attachment->ID) : $path_parts['filename']; similar_text(strtolower($term), strtolower($attachment_search_meta), $suggestion['similarity']); if ( $suggestion['similarity'] >= 50 ){ $suggestion['label'] = ( get_the_title($attachment->ID) != '' )? get_the_title($attachment->ID) : $path_parts['basename']; $suggestion['classes'] = 'type-attachment file-' . $path_parts['extension']; $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); if ( in_array(strtolower($path_parts['extension']), array('jpg', 'jpeg', 'png', 'gif', 'bmp')) ){ $suggestion['link'] = get_attachment_link($attachment->ID); } else { $suggestion['link'] = wp_get_attachment_url($attachment->ID); $suggestion['external'] = true; $suggestion['classes'] .= ' external-link'; } $suggestion['similarity'] = $suggestion['similarity']-0.001; //Force lower priority than posts/pages. $suggestions[] = $suggestion; $attachment_count++; } if ( $attachment_count >= 2 ){ break; } } } } } //Find menu items if ( !$this->in_array_any(array('menu'), $ignore_post_types) && $this->in_array_any(array('any', 'menu'), $types) ){ $menus = nebula()->transient('nebula_autocomplete_menus', function(){ return get_terms('nav_menu'); }, WEEK_IN_SECONDS); //This transient is deleted when a post is updated or Nebula Options are saved. foreach ( $menus as $menu ){ $menu_items = wp_get_nav_menu_items($menu->term_id); foreach ( $menu_items as $key => $menu_item ){ $suggestion = array(); similar_text(strtolower($term), strtolower($menu_item->title), $menu_title_similarity); similar_text(strtolower($term), strtolower($menu_item->attr_title), $menu_attr_similarity); if ( $menu_title_similarity >= 65 || $menu_attr_similarity >= 65 ){ if ( $menu_title_similarity >= $menu_attr_similarity ){ $suggestion['similarity'] = $menu_title_similarity; $suggestion['label'] = $menu_item->title; } else { $suggestion['similarity'] = $menu_attr_similarity; $suggestion['label'] = $menu_item->attr_title; } if ( !empty($menu_item->url) ){ $suggestion['link'] = $menu_item->url; $path_parts = pathinfo($menu_item->url); $suggestion['classes'] = 'type-menu-item'; if ( $path_parts['extension'] ){ $suggestion['classes'] .= ' file-' . $path_parts['extension']; $suggestion['external'] = true; } elseif ( !strpos($suggestion['link'], $this->url_components('domain')) ){ //@todo "Nebula" 0: Update strpos() to str_contains() in PHP8 $suggestion['classes'] .= ' external-link'; $suggestion['external'] = true; } $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); $suggestion['similarity'] = $suggestion['similarity']-0.001; //Force lower priority than posts/pages. $suggestions[] = $suggestion; break; } } } } } //Find categories if ( !$this->in_array_any(array('category', 'cat'), $ignore_post_types) && $this->in_array_any(array('any', 'category', 'cat'), $types) ){ $categories = nebula()->transient('nebula_autocomplete_categories', function(){ return get_categories(); }, WEEK_IN_SECONDS); //This transient is deleted when a post is updated or Nebula Options are saved. foreach ( $categories as $category ){ $suggestion = array(); $cat_count = 0; similar_text(strtolower($term), strtolower($category->name), $suggestion['similarity']); if ( $suggestion['similarity'] >= 65 ){ $suggestion['label'] = $category->name; $suggestion['link'] = get_category_link($category->term_id); $suggestion['classes'] = 'type-category'; $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); $suggestions[] = $suggestion; $cat_count++; } if ( $cat_count >= 2 ){ break; } } } //Find tags if ( !$this->in_array_any(array('tag'), $ignore_post_types) && $this->in_array_any(array('any', 'tag'), $types) ){ $tags = nebula()->transient('nebula_autocomplete_tags', function(){ return get_tags(); }, WEEK_IN_SECONDS); //This transient is deleted when a post is updated or Nebula Options are saved. foreach ( $tags as $tag ){ $suggestion = array(); $tag_count = 0; similar_text(strtolower($term), strtolower($tag->name), $suggestion['similarity']); if ( $suggestion['similarity'] >= 65 ){ $suggestion['label'] = $tag->name; $suggestion['link'] = get_tag_link($tag->term_id); $suggestion['classes'] = 'type-tag'; $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); $suggestions[] = $suggestion; $tag_count++; } if ( $tag_count >= 2 ){ break; } } } //Find authors (if author bios are enabled) if ( $this->get_option('author_bios') && !$this->in_array_any(array('author'), $ignore_post_types) && $this->in_array_any(array('any', 'author'), $types) ){ $authors = nebula()->transient('nebula_autocomplete_authors', function(){ return get_users(array('role' => 'author', 'has_published_posts' => true, 'role__not_in' => array('subscriber'))); }, WEEK_IN_SECONDS); //This transient is deleted when a post is updated or Nebula Options are saved. foreach ( $authors as $author ){ $author_name = ( $author->first_name != '' )? $author->first_name . ' ' . $author->last_name : $author->display_name; //might need adjusting here if ( strtolower($author_name) === strtolower($term) ){ //todo: if similarity of author name and query term is higher than X. Return only 1 or 2. $suggestion = array(); $suggestion['label'] = $author_name; $suggestion['link'] = get_author_posts_url($author->ID); $suggestion['classes'] = 'type-user'; $suggestion['classes'] .= $this->close_or_exact($suggestion['similarity']); $suggestion['similarity'] = ''; //todo: save similarity to array too $suggestions[] = $suggestion; break; } } } } //Now do stuff to the resulting suggestions array if ( !empty($suggestions) ){ if ( $should_be_sorted ){ //Order by match similarity to page title (DESC). function autocomplete_similarity_compare($a, $b){ return $b['similarity'] - $a['similarity']; } usort($suggestions, "autocomplete_similarity_compare"); } //Remove any duplicate links (higher similarity = higher priority) $outputArray = array(); //This array is where unique results will be stored $keysArray = array(); //This array stores values to check duplicates against. foreach ( $suggestions as $suggestion ){ if ( !in_array($suggestion['link'], $keysArray) ){ $keysArray[] = $suggestion['link']; $outputArray[] = $suggestion; } } $outputArray = array_slice($outputArray, 0, 9); //Limit to a maximum amount of results (they are already ordered by similarity) } //Add a link to search at the end of the list //@TODO "Nebula" 0: The empty result is not working for some reason... (Press Enter... is never appearing) $suggestion = array(); $suggestion['label'] = ( !empty($suggestions) )? __('...more results for', 'nebula') . ' "' . $term . '"' : __('Press enter to search for', 'nebula') . ' "' . $term . '"'; $suggestion['link'] = home_url('/') . '?s=' . str_replace(' ', '%20', $term); $suggestion['classes'] = ( !empty($suggestions) )? 'more-results search-link' : 'no-results search-link'; $outputArray[] = $suggestion; $this->timer($timer_name, 'end'); return $outputArray; } }
Override
This function can not be short-circuited with an override filter. Request one?