Search that will look in custom field, post title and post content

I am looking for a way to perform a search, and while performing the search, it checks a custom field named ‘keywords,’ the post title, and the post content. If any of those fields have results like what the user searches, it will display the custom post type (programs) in the results page.

I do not need help with the end display result (it currently does what I want it to do), but I need to make it so when it searches it checks all three fields (post title, post content, and custom field keywords), and if any of them have a result like what was searched, it displays the results. This is the code I have so far. It is currently only looking in the keywords custom field:

elseif($program_search) {
    // search by program search text
    query_posts(array(
    'post_type' => 'program',
    'meta_query' => array(
        array(
            'key' => 'keywords',
            'value' => $program_search,
            'compare' => 'LIKE'
        ),      
    )
    ));             
    if ( have_posts() ) : 
        while ( have_posts() ) : 
            the_post();
            $l.= "<div class='program-item'>";
                $l.= "<div class='program-item-image'><a href='".get_permalink($post->ID)."'>". get_the_post_thumbnail($post->ID, 'thumbnail')."</a></div>";
                $l.= "<div class='program-item-title'><a href='".get_permalink($post->ID)."'>".get_the_title($post->ID)."</a></div>";
                $l.= "<div class='program-item-content'>".get_the_excerpt()."</div>";
                $l.= "<div style='clear:both;'></div>";
            $l.= "</div>";
         endwhile; 
    else:
    endif;
}

Related posts

1 comment

  1. First, don’t use query_posts.

    Second, you can pass an s parameter to get most of the way there.

    $program_search = 'test';
    $args = array(
      'post_type' => 'program',
      's' => $program_search,
      'meta_query' => array(
        array(
          'key' => 'keywords',
          'value' => $program_search,
          'compare' => 'LIKE'
        ),      
      )
    );
    $t = new WP_Query($args);
    var_dump($t->request);
    

    That s parameter kicks the ordinary search mechanisms into place and the title and the content gets searched. If you look at that generated query you will see…

    SELECT 
      SQL_CALC_FOUND_ROWS 
      wp_posts.ID 
    FROM wp_posts 
    INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id) 
    WHERE 1=1 
    AND (((wp_posts.post_title LIKE '%test%') OR (wp_posts.post_content LIKE '%test%'))) 
    AND (wp_posts.post_password = '') 
    AND wp_posts.post_type = 'program' 
    AND (wp_posts.post_status = 'publish') 
    AND ((wp_postmeta.meta_key = 'keywords' AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%test%')) 
    GROUP BY wp_posts.ID 
    ORDER BY wp_posts.post_date DESC 
    LIMIT 0, 5
    

    That is most of what you want. The LIMIT unless otherwise specified is the limit set at wp-admin->Settings->General. There is a problem though.

    AND ((wp_postmeta.meta_key = 'keywords' AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%test%')) 
    

    I am pretty sure you want that to be OR ((wp_postmeta.meta_key ... and you really want it up with the post_title and the post_content too. Something like this:

    AND (
      (
        (wp_posts.post_title LIKE '%test%') 
        OR 
        (wp_posts.post_content LIKE '%test%')
        OR 
        (wp_postmeta.meta_key = 'keywords' AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%test%')
      )
    ) 
    

    WP_Query won’t do that so we have to make it with some filters. Proof of concept:

    function add_join_wpse_99849($joins) {
      global $wpdb;
      return $joins . " INNER JOIN {$wpdb->postmeta} ON ({$wpdb->posts}.ID = {$wpdb->postmeta}.post_id)";
    }
    
    function alter_search_wpse_99849($search,$qry) {
      global $wpdb;
      $add = $wpdb->prepare("({$wpdb->postmeta}.meta_key = 'keywords' AND CAST({$wpdb->postmeta}.meta_value AS CHAR) LIKE '%%%s%%')",$qry->get('s'));
      $pat = '|(((.+)))|';
      $search = preg_replace($pat,'(($1 OR '.$add.'))',$search);
      return $search;
    }
    
    $program_search = 'test';
    $args = array(
      'post_type' => 'program',
      's' => $program_search
    );
    
    add_filter('posts_join','add_join_wpse_99849');
    add_filter('posts_search','alter_search_wpse_99849',1,2);
    $t = new WP_Query($args);
    remove_filter('posts_join','add_join_wpse_99849');
    remove_filter('posts_search','alter_search_wpse_99849',1,2);
    
    // dump some data
    var_dump($t->request);
    var_dump($t->posts);
    

    Notice that I left out the meta_query altogether and largely duplicated the functionality. That is to keep that troublesome AND from being generated.

    You are applying and immediately removing those filters so they do not interfere with any other queries. There are other ways to keep the filter out of the way or other queries. One such method is outlined here. You can also add the remove_filter to the add_filter callback to have them automatically remover themselves.

Comments are closed.