How to create an advanced search form for WordPress

How to make a basic search form in PHP

First of all, I needed the basics. If you are a front end web developer like myself and don’t really know much about exactly how PHP interacts with a MySQL database, then this series of videos from Adam Khoury will give you a great introduction to how a database is set up, how MySQL gets information from the database, and how PHP and HTML work to make MySQL do this:

The Advanced Search Form

OK, so to begin with you need to set up the form itself. This is just some fairly straightforward HTML:

[codesyntax lang=”html4strict”]

<form role="search" method="get" id="searchform" action="http://localhost:8888/search">
	<ul id="mysearchul">
    	<li><span class="sch">Course Type:</span>
            <select name="coursetype">
            	<option value=''>Select a course</option>
                <?php
                $args = array('numberposts'=>99, 'child_of'=>2);
                $cats = get_categories($args);
                foreach($cats as $cat) { ?>
                <option value="<?php echo $cat->cat_ID; ?>"><?php echo $cat->cat_name; ?></option>
                <?php } ?>
            </select></li>

        <li><span class="sch">Course Date:</span>
        	<select id="datelogic" name="datelogic">
            	<option value="Before">Before</option>
                <option value="After">After</option>
                <option value="On">On</option>
            </select>
            <input type="date" name="inputdate" /> </li>
        <li><span class="sch">Keyword:</span>
        	<input type="text" name="keyword" /></li>

        <li><input type="submit" id="searchsubmit" value="Search!" /></li>
   </ul>	
</form>

[/codesyntax]

So what does this do? Well first of all there is <form> tag that everything is wrapped around.┬áThis holds the four different inputs. I’ve also placed them in a <ul> tag to make CSS easier to edit.

The first <li> tag holds the ‘course type’ drop down menu, and makes use of WordPress’ get_categories() function to create this list.

The second <li> tag deals with the ‘course date’ input and displays a drop down menu for the ‘course logic’ input, and then uses HTML5’s ‘date’ input type to create a pop up calendar.

The third <li> tag contains the keyword, which is just a simple text input.

Remember that each input needs a “name” so that it can be used to do stuff.

The “action” in the form tells WordPress to go to another page to display the search results. You will need to create a new search page (which I called “page-search.php”) as it seems it would be way too complicated to make the results show on the usual “search.php” page.

You also need the id, which is “searchform”, and the method, which is “get”.

The Search Page

As stated above, the results get shown on a new search page. On this search page is this code:

[codesyntax lang=”php”]

<?php if ( ($_GET['keyword'] == '') && ($_GET['inputdate'] == '') && ($_GET['coursetype'] == '') ) { ?>
		<!-- if all the input fields are empty, then advise that the search is empty -->	 
		<h1 class="pagetitle">Empty Search</h1>

<?php } else { ?>
		<h1 class="pagetitle"><?php the_title(); ?> Results</h1>

		<?php $searched_posts = mysearch();
			foreach ($searched_posts as $post) {
				$id = $post->ID; 
				$args = array('p'=>$id);
				$query = new WP_Query('p=$id'); ?>

        		<div class="post">
                    <div class="newsimg"><?php echo get_the_post_thumbnail($id, 'newsimg'); ?></div>

                    <div class="postinfo">
                        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                        <?php echo $post->post_excerpt; ?>
                        <?php if(in_category('courses')) { ?>
                            <a class="shinyblue button" href="<?php the_permalink(); ?>">Learn more about this course</a>
                        <?php } else { ?>
                            <a class="shinyblue button" href="<?php the_permalink(); ?>">Read more...</a>
                        <?php } ?>
                    </div>
               	<div class="clearer"></div>
             	</div>
<?php } } ?>

[/codesyntax]

This basically just calls a function called mysearch(), which we will place into the functions.php file in a moment. mysearch() creates an array of objects which are your search results. The foreach loop simply takes the data from these results and turns them into readable HTML.

How it’s done

The active function is called mysearch() and is placed into the functions.php file to be tidy, although technically you could place it into the page-search.php file as it’s not going to be used anywhere else.

It looks like this:

[codesyntax lang=”php”]

<?php function mysearch () {
	global $wpdb;
	$coursetype = preg_replace('/[^0-9]/', '', $_GET['coursetype']);
	$pinputdate = preg_replace('/[^0-9]/', '', $_GET['inputdate']);
	$datelogic = preg_replace('/[^a-zA-Z]/', '', $_GET['datelogic']);
	$keyword = preg_replace('/^-|-$|[^-a-zA-Z0-9]/', '', $_GET['keyword']);
	// This replaces characters in the input string if need be for security reasons

	if($coursetype != '') {
		$args = array( 'category'=>$coursetype );
		$courseposts = get_posts($args); //gets posts with the input category name
		$i = 0;
		$courseresults = NULL; //empties the courseresults array in case there's anything in it from a previous serach

		if($courseposts != NULL) {
			foreach($courseposts as $coursepost) : //creates an array of IDs to be used later on
				$courseid = $coursepost->ID;
				$courseresults[$i] = $courseid;	
				$i++; 
			endforeach;
		} 
	} 

	if($keyword != '') {
		$keyword_mysql = "SELECT * FROM $wpdb->posts WHERE post_type='post' AND post_title LIKE '%$keyword%' OR post_content LIKE '%$keyword%'";
		if($courseresults != '') {
			$keyword_mysql .= " AND";
			foreach($courseresults as $courseresult) {
				$keyword_mysql .= " ID = '%$courseresult%' OR"; //if the course type query yielded results, then this appends those results to the output of this query
			}
			$keyword_mysql .= " ID = '%$courseresult%'";
		}
		$query .= $keyword_mysql;

		echo '<p>'. $query. '</p>';
		$results = $wpdb->get_results($query); //this generates an array of all the results so far
	} elseif($courseresults != '') {
		$results = $courseposts; //if no keyword has been entered, but a course type has, then the results so far will be the same as the course type results
	}

	if($pinputdate != '') :
		if(($keyword == '') && ($coursetype == '')) {
			$results = get_posts('category=2'); //get all the courses if only a date has been entered
		}

		$i = 0;	
		$finalresults = NULL;
		foreach($results as $result) {
			$id = $post->ID;
			$exampledate = get_post_meta($id, 'course_dates', true);
			if((($datelogic == 'Before') && ($exampledate < $pinputdate)) || (($datelogic == 'After') && ($exampledate > $pinputdate)) || (($datelogic == 'On') && ($exampledate == $pinputdate))) { 
			//filters the results based on the date logic and input date
				$finalresults[$i] = $result;
				$i++;
			} 
		}
		$results = $finalresults;
	endif;

	if(($results == 0) || ($results == false) || ($results == NULL)) :
		echo '<p>Sorry, there seem to be no results for your request. Try again or search using the navigation menu above.</p>';
		$results = NULL;
		return $results;
	else :
		return $results;
	endif; 

} // END OF MYSEARCH ?>

[/codesyntax]

OK, I’ve done what I can to annotate these results, but basically this creates an array of posts and filters the array each time based on what inputs have been entered.

Things to note are:

Make sure you use the preg_replace() function to parse the inputs, otherwise hackers can do all kinds of horrible things with your database.

The ‘Course type’ loop just creates an array of posts using WordPress’ get_posts() function.

The ‘Keyword’ loop uses WordPress’ $wpdb->get_results() function, which takes a MySQL command and gets stuff from the database based on that. In order to keep any results that were produced by the ‘Course type’ loop, then the IDs for these posts get added to the MySQL command so that they are returned by this loop as well.

The ‘Course Date’ loop just takes all the results so far and compares the ‘date logic’ and ‘input date’ inputs and only keeps the results that fit the appropriate criteria. If no results have been generated by the ‘Course Type’ or ‘Keyword’ loops, then it takes all the posts from the ‘courses’ category and uses those as the results so far.

By the end, you will have an array of posts, which then get formatted by the page-search.php page.

Are you better at this than me?

I have a feeling there may be a more efficient way to do this that my simple designer/front end developer mind can’t work out.

If you know of a more elegant solution, then let me know in the comments below.

Leave a comment