Skip to content

Instantly share code, notes, and snippets.

@carlodaniele
Last active December 14, 2024 11:22
Show Gist options
  • Save carlodaniele/fd5847f4baf42ca84771273ce21771fd to your computer and use it in GitHub Desktop.
Save carlodaniele/fd5847f4baf42ca84771273ce21771fd to your computer and use it in GitHub Desktop.
Building An Advanced WordPress Search With WP_Query https://www.smashingmagazine.com/2016/03/advanced-wordpress-search-with-wp_query/
<?php
/**
* @package Smashing_plugin
* @version 1.0
*/
/*
Plugin Name: Smashing plugin
Plugin URI: https://www.smashingmagazine.com/2016/03/advanced-wordpress-search-with-wp_query/
Description: This is an example plugin for Smashing Magazine readers.
Author: Carlo Daniele
Version: 1.0
Author URI: http://carlodaniele.it/en/
*/
/**
* Plugin setup
* Register post type and shortocode
*/
function sm_setup() {
$labels = array(
'name' => __( 'Accommodation', 'smashing_plugin' ),
'singular_name' => __( 'Accommodation', 'smashing_plugin' ),
'add_new_item' => __( 'Add New Accommodation', 'smashing_plugin' )
);
$args = array(
'labels' => $labels,
'description' => '',
'public' => true,
'show_ui' => true,
'has_archive' => true,
'show_in_menu' => true,
'exclude_from_search' => false,
'capability_type' => 'post',
'map_meta_cap' => true,
'hierarchical' => false,
'rewrite' => array( 'slug' => 'accommodation', 'with_front' => true ),
'query_var' => true,
'menu_icon' => 'dashicons-admin-multisite',
'supports' => array( 'title', 'editor', 'thumbnail', 'author', 'custom-fields' ),
'register_meta_box_cb' => 'sm_add_meta_boxes',
'taxonomies' => array( 'typology' )
);
register_post_type( 'accommodation', $args );
add_shortcode( 'sm_search_form', 'sm_search_form' );
}
add_action( 'init', 'sm_setup' );
/**
* Register custom taxonomy
*/
function sm_register_custom_taxonomies() {
$labels = array(
'name' => __( 'Typologies', 'smashing_plugin' ),
'label' => __( 'Typologies', 'smashing_plugin' ),
'add_new_item' => __( 'Add new Typology', 'smashing_plugin' ),
);
$args = array(
'labels' => $labels,
'hierarchical' => true,
'label' => __( 'Typologies', 'smashing_plugin' ),
'show_ui' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'typology', 'with_front' => true ),
'show_admin_column' => true,
);
register_taxonomy( 'typology', array( 'accommodation' ), $args );
}
add_action( 'init', 'sm_register_custom_taxonomies' );
/**
* Add custom meta boxes
*/
function sm_add_meta_boxes(){
add_meta_box( 'sm_features_meta_box', __( 'Features', 'smashing_plugin' ), 'sm_build_features_meta_box', 'accommodation', 'side' );
add_meta_box( 'sm_location_meta_box', __( 'Location', 'smashing_plugin' ), 'sm_build_location_meta_box', 'accommodation', 'normal' );
}
/**
* Print custom meta box
*/
function sm_build_features_meta_box( $post ){
// make sure the form request comes from WordPress
wp_nonce_field( basename( __FILE__ ), 'sm_features_meta_box_nonce' );
// retrieve current field values
$type = get_post_meta( $post->ID, '_sm_accommodation_type', true );
$facilities = get_post_meta( $post->ID, '_sm_accommodation_facilities', false );
// an array of default facilities
$available_facilities = array( 'essentials', 'kitchen', 'internet', 'parking', 'tv', 'washer', 'wireless' );
?>
<div class='inside'>
<p><strong>Type of accommodation</strong></p>
<p>
<select name="customfields[type]">
<option value="" <?php selected( $type, "" ); ?>>Select</option>
<option value="entire" <?php selected( $type, 'entire' ); ?>><?php _e( 'Entire house', 'smashing_plugin' ); ?></option>
<option value="private" <?php selected( $type, 'private' ); ?>><?php _e( 'Private room', 'smashing_plugin' ); ?></option>
<option value="shared" <?php selected( $type, 'shared' ); ?>><?php _e( 'Shared room', 'smashing_plugin' ); ?></option>
</select>
</p>
<p><strong><?php _e( 'Facilities', 'smashing_plugin' ); ?></strong></p>
<p>
<?php
foreach ( $available_facilities as $f ) {
if( !isset( $facilities[0][$f] ) ){
$facilities[0][$f] = 0;
}
?>
<input type="checkbox" name="customfields[facilities][<?php echo $f; ?>]" value="<?php echo $f; ?>" <?php checked( $facilities[0][$f], $f ); ?>><?php echo ucfirst($f); ?><br />
<?php
}
?>
</p>
</div>
<?php
}
/**
* Print custom meta box
*/
function sm_build_location_meta_box( $post ){
wp_nonce_field( basename( __FILE__ ), 'sm_location_meta_box_nonce' );
$custom_fields = get_post_custom( $post->ID );
$address = isset( $custom_fields['_sm_accommodation_address'][0] ) ? $custom_fields['_sm_accommodation_address'][0] : '';
$city = isset( $custom_fields['_sm_accommodation_city'][0] ) ? $custom_fields['_sm_accommodation_city'][0] : '';
$country = isset( $custom_fields['_sm_accommodation_country'][0] ) ? $custom_fields['_sm_accommodation_country'][0] : '';
?>
<div class="inside">
<p><strong>Address</strong></p>
<p><input type="text" id="sm_accommodation_address" name="customfields[address]" value="<?php echo esc_attr( $address ); ?>" /></p>
<p><strong>City</strong></p>
<p><input type="text" id="sm_accommodation_city" name="customfields[city]" value="<?php echo esc_attr( $city ); ?>" /></p>
<p><strong>Country</strong></p>
<p><input type="text" id="sm_accommodation_country" name="customfields[country]" value="<?php echo esc_attr( $country ); ?>" /></p>
</div>
<?php
}
/**
* Store custom meta box data
*/
function sm_save_meta_boxes_data( $post_id ){
// verify first meta box nonce
if ( !isset( $_POST['sm_features_meta_box_nonce'] ) || !wp_verify_nonce( $_POST['sm_features_meta_box_nonce'], basename( __FILE__ ) ) ){
return;
}
// verify second meta box nonce
if ( !isset( $_POST['sm_location_meta_box_nonce'] ) || !wp_verify_nonce( $_POST['sm_location_meta_box_nonce'], basename( __FILE__ ) ) ){
return;
}
// return if autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ){
return;
}
// Check the user's permissions.
if ( isset( $_POST['post_type'] ) && 'accommodation' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_post', $post_id ) ){
return;
}
}
// custom fields values
$customfields = ( isset( $_POST['customfields'] ) ) ? (array) $_POST['customfields'] : array();
if( count( $customfields ) == 0 ){
return;
}
foreach ($customfields as $key => $value) {
// checks if is bidimensional array
// and sanitize values
if( is_array( $value ) ){
$value = array_map( 'sanitize_text_field', $value );
// if not an array
// sanitize value
}else{
$value = sanitize_text_field( $value );
}
update_post_meta( $post_id, '_sm_accommodation_' . $key, $value );
}
}
add_action( 'save_post', 'sm_save_meta_boxes_data' );
/**
* Register custom query vars
*
* @link https://codex.wordpress.org/Plugin_API/Filter_Reference/query_vars
*/
function sm_register_query_vars( $vars ) {
$vars[] = 'type';
$vars[] = 'city';
return $vars;
}
add_filter( 'query_vars', 'sm_register_query_vars' );
/**
* Build a custom query based on several conditions
* The pre_get_posts action gives developers access to the $query object by reference
* any changes you make to $query are made directly to the original object - no return value is requested
*
* @link https://codex.wordpress.org/Plugin_API/Action_Reference/pre_get_posts
*
*/
function sm_pre_get_posts( $query ) {
// check if the user is requesting an admin page
// or current query is not the main query
if ( is_admin() || ! $query->is_main_query() ){
return;
}
// edit the query only when post type is 'accommodation'
// if it isn't, return
if ( !is_post_type_archive( 'accommodation' ) ){
return;
}
$meta_query = array();
// get query var values
// defaults to empty string
if( !empty( get_query_var( 'city' ) ) ){
$meta_query[] = array( 'key' => '_sm_accommodation_city', 'value' => get_query_var( 'city' ), 'compare' => 'LIKE' );
}
if( !empty( get_query_var( 'type' ) ) ){
$meta_query[] = array( 'key' => '_sm_accommodation_type', 'value' => get_query_var( 'type' ), 'compare' => 'LIKE' );
}
if( count( $meta_query ) > 1 ){
$meta_query['relation'] = 'AND';
}
if( count( $meta_query ) > 0 ){
$query->set( 'meta_query', $meta_query );
}
}
add_action( 'pre_get_posts', 'sm_pre_get_posts', 1 );
/**
* Build search form markup
*/
function sm_search_form( $args ){
// The Query
// meta_query expects nested arrays, even if you only have one query
// to add the category param
$sm_query = new WP_Query( array( 'post_type' => 'accommodation', 'posts_per_page' => '-1', 'meta_query' => array( array( 'key' => '_sm_accommodation_city' ) ) ) );
// The Loop
if ( $sm_query->have_posts() ) {
$cities = array();
while ( $sm_query->have_posts() ) {
$sm_query->the_post();
$city = get_post_meta( get_the_ID(), '_sm_accommodation_city', true );
// populate an array of all occurrences (non duplicated)
if( !in_array( $city, $cities ) ){
$cities[] = $city;
}
}
}
/* Restore original Post Data */
wp_reset_postdata();
if( count($cities) == 0){
return;
}
asort($cities);
$select_city = '<select name="city">';
$select_city .= '<option value="">' . __( 'Select city', 'smashing_plugin' ) . '</option>';
foreach ($cities as $city ) {
$select_city .= '<option value="' . $city . '">' . $city . '</option>';
}
$select_city .= '</select>' . "\n";
reset($cities);
$args = array( 'hide_empty' => false );
$typology_terms = get_terms( 'typology', $args );
if( is_array( $typology_terms ) ){
$select_typology = '<select name="typology">';
$select_typology .= '<option value="" selected="selected">' . __( 'Select typology', 'smashing_plugin' ) . '</option>';
foreach ( $typology_terms as $term ) {
$select_typology .= '<option value="' . $term->slug . '">' . $term->name . '</option>';
}
$select_typology .= '</select>' . "\n";
}
$select_type = '<select name="type">';
$select_type .= '<option value="" selected="selected">' . __( 'Select room type', 'smashing_plugin' ) . '</option>';
$select_type .= '<option value="entire">' . __( 'Entire house', 'smashing_plugin' ) . '</option>';
$select_type .= '<option value="private">' . __( 'Private room', 'smashing_plugin' ) . '</option>';
$select_type .= '<option value="shared">' . __( 'Shared room', 'smashing_plugin' ) . '</option>';
$select_type .= '</select>' . "\n";
$output = '<form id="smform" action="' . esc_url( home_url() ) . '" method="GET" role="search">';
$output .= '<div class="smtextfield">' . '<input type="text" name="s" placeholder="Search key..." value="' . get_search_query() . '" /></div>';
$output .= '<div class="smselectbox">' . $select_city . '</div>';
$output .= '<div class="smselectbox">' . $select_typology . '</div>';
$output .= '<div class="smselectbox">' . $select_type . '</div>';
$output .= '<input type="hidden" name="post_type" value="accommodation" />';
$output .= '<p><input type="submit" value="Go!" class="button" /></p></form>';
return $output;
}
@kr3t3n
Copy link

kr3t3n commented Feb 19, 2017

Hi, thank you very much for this tutorial! I am trying to apply it on a small website using custom taxonomy with Parent-Child relation and am wondering how to make the input form field to dynamically load CHILD values based on the user selected PARENT. If possible, can you illustrate how would that work?

Thank you very much in advance!

Regards,

Georgi

@MichaelChristman
Copy link

MichaelChristman commented Mar 7, 2017

Hey Carlo,

First I wanted to say that this article has been extremely helpful. I am curious if there is a simple way to restrict the text search field to search only within the archive page of the custom post type.

From your article I gather the name = "s" attribute of the text input element is where I should look, however I am uncertain what I should change it to, or if this is more than just a simple tweak.

Any help regarding this matter is greatly appreciated.
Cheers,
Michael

edit: I have found a simple solution for this here for those looking to do the same

@carlodaniele
Copy link
Author

Hi Georgi,
you may use the get_term_children function. It returns an array of all term children. You could iterate over the elements of the array to build the required options.

Michael,
you cas specifically declare the post type you're searching for posts when you build the query. I used the is_post_type_archive (line 244), but you have many ways to set a specific post type. The article you've highlighted provides a good solution.

@mf-7
Copy link

mf-7 commented Oct 20, 2018

How do we call the search box in a webpage after we activate the plugin?

Edit:

I belive iv found the solution. Please tell me if this is right:

echo sm_search_form($args);

Iv got the search box with the fields to choose :)

@mehmetsarr
Copy link

Hello, thank you for the article. I will show how this will ultimately.

My example loop code is below:

	<?php

query_posts( array( 'post_type' => 'ilanlar') );
if ( have_posts() ) : while ( have_posts() ) : the_post();
?>

                        <div class="row item col-xs-12 col-sm-6 col-md-6 col-lg-4 col-xl-4"> 
                            <div class="mdc-card property-item grid-item column-3">
                                <div class="thumbnail-section">
                                    <div class="row property-status">
                                    </div> 
                                    <div class="property-image"> 
                                        <div class="swiper-container">
                                            <div class="swiper-wrapper"> 
                                                <div class="swiper-slide">
												<?php if ( has_post_thumbnail()) : ?>
                                                    <img src="<?php echo get_the_post_thumbnail_url();?>" alt="<?php the_title(); ?>" data-src="<?php echo get_the_post_thumbnail_url();?>" class="slide-item swiper-lazy">
                                                     <?php else: ?>
                               <?php endif;?>
													<div class="swiper-lazy-preloader"></div> 
                                                </div> 
                                            </div>  
                                          </div>  
                                    </div>  
                                </div>
                                <div class="property-content-wrapper"> 
                                    <div class="property-content">
                                        <div class="content">
                                            <h1 class="title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h1>
                                            <p class="row address flex-nowrap">
                                                <i class="material-icons text-muted">location_on</i>
                                                <span><?php echo get_post_meta(get_the_ID(), "menkuladres_ilanadresi", true); ?></span>
                                            </p>
                                            <div class="row between-xs middle-xs">
                                                <h3 class="primary-color price">
                                                    <span><?php echo get_post_meta(get_the_ID(), "menkul_ilanfiyati", true); ?> TL</span> 
                                                </h3> 
                                            </div>
                                            <div class="d-none d-md-flex d-lg-flex d-xl-flex">
                                                <div class="description mt-3"> 
                                                    <p><?php the_excerpt(); ?></p>
                                                </div>
                                            </div>
                                            <div class="features mt-3">                    
                                        <p><span>İlan No</span><span><?php the_ID(); ?></span></p>
										<p><span>İlan Durumu</span><span><?php the_terms( $post->ID, 'ilandurumu'); ?></span></p>
                                        <p><span>Konut Tipi</span><span><?php the_terms( $post->ID, 'ilankonuttipi'); ?></span></p>
                                        <p><span>M2</span><span><?php echo get_post_meta(get_the_ID(), "menkul_m2net", true); ?></span></p>
                                        <p><span>Oda Sayısı</span><span><?php echo get_post_meta(get_the_ID(), "menkul_odasayisi", true); ?></span></p>
                                            </div>   
                                        </div> 
                                        <div class="grow"></div>
                                        <div class="actions row between-xs middle-xs">
                                            <p class="row date mb-0">
                                                <i class="material-icons text-muted">date_range</i>
                                                <span class="mx-2"><?php the_time('j F, Y'); ?></span>
                                            </p> 
                                            <a href="<?php the_permalink(); ?>" class="mdc-button mdc-button--outlined">
                                                <span class="mdc-button__ripple"></span>
                                                <span class="mdc-button__label">DETAYLAR</span> 
                                            </a>  
                                        </div>
                                    </div>  
                                </div> 
                            </div>  
                        </div>  
							<?php endwhile; endif; wp_reset_query(); ?>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment