Displaying Content
on Your Terms
with WP_Query

What are we going to learn today?

About Jim

What is WP_Query?

What is WP_Query?

WP_Query is what makes the WordPress go around.

What is WP_Query?

Basic structure.

				
// WP_Query arguments
$args = array(
	'post_type'              => array( 'books' ), //Can be 'posts', 'pages'
	'post_status'            => array( 'publish', ' private' ), // 'draft', 'scheduled' are also options
	'nopaging'               => false,
	'paged'                  => '5',
	'posts_per_page'         => '50',
	'order'                  => 'DESC',
	'orderby'                => 'title',
);

// The Query
$query = new WP_Query( $args );

// The Loop
if ( $query->have_posts() ) {
	while ( $query->have_posts() ) {
		$query->the_post();
		// do something
	}
} else {
	// no posts found
}

// Restore original Post Data
wp_reset_postdata();
				
			
Examples

Page "archive" of STAT Plus posts

				
function stat_the_paywall_loop() {
	$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
	$paywall_posts = method_exists( '\STAT\Paywall\Admin', 'get_paywall_posts' ) ? \STAT\Paywall\Admin::get_paywall_posts( null, $paged ) : '';
	if ( empty( $paywall_posts ) || ! class_exists( '\STAT\Paywall\Archive' ) ) {
		return;
	}
	if ( is_page_template( \STAT\Paywall\Archive::get_instance()->template_path_user ) && $paywall_posts ) {
		global $wp_query;
		$paywall_posts->is_home = false; //We don't want homepage-based ads
		$paywall_posts->is_category = true; // We DO want category-based ads
		$paywall_posts->is_author = true; // Because we are setting it as a category, we lose the kickers.  Tricking the query to treat it as an author page brings them back.
		$orig = $wp_query;
		// @codingStandardsIgnoreStart
		$wp_query = $paywall_posts;
		get_template_part( 'template-parts/content', 'loop' );
		$wp_query = $orig;
		// @codingStandardsIgnoreEnd
	}
	wp_reset_postdata();
}

function get_paywall_posts( $limit = null, $paged = null ) {
	$_posts = null;

	//Default WP_Query args
	$args = array(
		'post_type' => 'post',
		'post_status' => 'publish',
		'orderby' => 'post_date',
		'order' => 'DESC',
		'meta_query' => array(
			array(
				'key' => 'stat_paywall_restrict',
				'value' => true,
			),
		),
	);

	//If a limit has been passed in, use that to restrict the posts returned.
	//Otherwise, use the default limit.
	if ( null !== $limit ) {
		$args['posts_per_page'] = $limit;
	}

	// Check for the paged
	if ( null !== $paged ) {
		$args['paged'] = $paged;
	}

	$query = new WP_Query( $args );
	if ( $query->have_posts() ) {
		$_posts = $query;
	}
	wp_reset_postdata();
	return $_posts;
}
				
			
Examples

Prepend Zoninator posts before the main Loop

				
function update_home_query( $query ) {
	if ( ! $query->is_main_query() || ! $query->is_home() ) {
		return;
	}

	if ( ! function_exists( 'z_get_posts_in_zone' ) ) {
		return;
	}

	//Get the war story if it exists.
	$war_id = self::get_war_zone_post_id();
	if ( $war_id ) {
		$war_id = array( $war_id );
	} else {
		$war_id = array();
	}

	//Grab the existing post__not_in
	$current_not_in = $query->get( 'post__not_in' );

	/**
	 * We want the ability for the editors to populate the home page with the majority of
	 * posts from Zoninator.  However, since WP needs something to populate the home page, and
	 * doesn't really like posts_per_page to be 0, I'm giving it the bottom row.  This will
	 * allow the editors to have all but the bottom three posts to work with.
	 */
	$posts_per_page = get_option( 'posts_per_page' );
	$this->zone_posts_amt = $posts_per_page - 3;
	self::$home_zone_posts = z_get_posts_in_zone( self::$home_zone_slug, array( 'posts_per_page' => $this->zone_posts_amt, 'post__not_in' => $war_id ) );
	$home_zone_post_ids = wp_list_pluck( self::$home_zone_posts, 'ID' );
	$this->zone_count = count( self::$home_zone_posts );

	if ( ! $query->is_paged ) {
		$query->set( 'posts_per_page', ( $posts_per_page - $this->zone_count ) );
	} else {
		$offset = ( ( $query->query_vars['paged'] - 1 ) * $posts_per_page ) - $this->zone_count;
		$query->set( 'posts_per_page', $posts_per_page );
		$query->set( 'offset', $offset );
	}

	$home_n_war_zones = array_merge( $home_zone_post_ids, $war_id );
	$post_not_in = array_merge( $current_not_in, $home_n_war_zones );
	$query->set( 'post__not_in', $post_not_in );
}
				
			
Examples

Using the $query global to filter taxonomies.

				
function stat_exclude_taxonomy( $query, $taxonomy, $terms = array() ) {
	$field = 'slug';
	$tax_query = $query->get( 'tax_query' );

	if ( ! $tax_query ) {
		$tax_query = array();
	}

	if ( ! count( $terms ) ) {
		// $terms is empty, we're making the assumption that caller wants to exclude
		// all terms of the given $taxonomy for this $query.
		$term_ids = get_terms( array(
			'taxonomy' => $taxonomy,
			'fields' => 'ids',
		) );

		if ( is_array( $term_ids ) ) {
			$field = 'term_id';
			$terms = $term_ids;
		}
	}

	$tax_query[] = array(
		'taxonomy' => $taxonomy,
		'field' => $field,
		'terms' => $terms,
		'operator' => 'NOT IN',
	);

	$query->set( 'tax_query', $tax_query );
}
				
			
Examples

Using the meta_query paramater

				
function zone_schedule( $action ) {
	if ( ! in_array( $action, array( 'start', 'end' ), true ) || ! function_exists( 'z_get_zoninator' ) ) {
		return;
	}

	switch ( $action ) {
		case 'start':
			$key = self::$metabox_name . '_schedule_start_date';
			break;
		case 'end':
			$key = self::$metabox_name . '_schedule_end_date';
			break;
		default:
			break;
	}

	$now = time();

	$args = array(
		'post_type' => StatSponsoredContent::$post_type,
		'post_status' => 'publish',
		'posts_per_page' => 20, // Process max 20 Sponsor Pages per call.
		'meta_query' => array(
			'relation' => 'AND',
			array(
				'key' => self::$metabox_name . '_schedule_enable',
				'value' => 1,
			),
			array(
				'key' => $key,
				'compare' => '<=',
				'value' => $now,
				'type' => 'NUMERIC',
			),
		),
	);

	if ( 'start' === $action ) {
		// Add additional meta query clause for 'start' action to prevent us
		// from attempting to process Sponsor Pages that have already passed
		// their scheduled end date.
		$args['meta_query'][] = array(
			'key' => self::$metabox_name . '_schedule_end_date',
			'compare' => '>=',
			'value' => $now,
			'type' => 'NUMERIC',
		);
	}

	$query = new WP_Query( $args );

	while ( $query->have_posts() ) {
		$query->the_post();
		$_post = get_post();
		$zone_id = (int) $_post->{self::$metabox_name . '_zone_id'};

		if ( ! $zone_id ) {
			// Scheduling was enabled but no Zone was selected, skip this Post.
			continue;
		}

		switch ( $action ) {
			case 'start':
				$this->add_sponsor_to_zone( $zone_id, $_post );
				break;
			case 'end':
				z_get_zoninator()->remove_zone_posts( $zone_id, $_post );
				// Clear the current Post's schedule enable flag so that we don't
				// attempt to remove it from the Zone again during the next cron run.
				delete_post_meta( $_post->ID, self::$metabox_name . '_schedule_enable' );
				break;
			default:
				break;
		}
	}
	wp_reset_postdata();
}
				
			
Examples

Using the $wpdb global

				
public function action_comments_custom_column( $column, $comment_id ) {
	global $wpdb;
	$cats = array();
	if ( 'category' === $column ) {
		// @codingStandardsIgnoreStart
		$term_ids = $wpdb->get_col( $wpdb->prepare(
			"SELECT term.term_taxonomy_id FROM {$wpdb->term_relationships} as term "
			. "JOIN {$wpdb->term_taxonomy} as tax "
			. "ON term.term_taxonomy_id = tax.term_id "
			. "JOIN {$wpdb->posts} as post "
			. "ON term.object_id = post.ID "
			. "JOIN {$wpdb->comments} as comments "
			. "ON post.ID = comments.comment_post_ID "
			. "WHERE tax.taxonomy = 'category' "
			. "AND comments.comment_ID = '%s'",
			$comment_id
		) );
		// @codingStandardsIgnoreEnd
		if ( ! empty( $term_ids ) ) {
			foreach ( $term_ids as $id ) {
				$cat = get_category( $id );
				$cats[] = $cat->name;
			}
			$cat_implosion = implode( ', ', $cats );
			echo esc_html( $cat_implosion );
		}
	}
}
				
			
Examples

Interacting with the posts_search filter

				
public function filter_coauthors_search( $posts_search, $query ) {
	global $wpdb, $coauthors_plus;

	$search = $this->get_search( $query );

	// Don't modify the query at all if we're not on the search template
	// or if the LIKE is empty
	if ( empty( $posts_search ) || ! $search ) {
		return $posts_search;
	}

	// Find the author "term_ids" based on the search query
	$co_terms = get_terms( $coauthors_plus->coauthor_taxonomy, array(
		'orderby' => 'name',
		'hide_empty' => false,
		'description__like' => $search,
	) );

	// Don't modify the query if there aren't any matching authors
	if ( empty( $co_terms ) ) {
		return $posts_search;
	}

	$co_ids = wp_list_pluck( $co_terms, 'term_taxonomy_id' );

	// Add the tax_ids to the search string.
	$posts_search = str_replace( ')))', ")) OR ( {$wpdb->term_relationships}.term_taxonomy_id IN(" . implode( ',', $co_ids ) . ')))', $posts_search );

	return $posts_search;
}

/**
 * Add a join on term_relationships to allow
 * searching on coauthors term
 * @global object $wpdb
 * @param string $join
 * @return string
 */
public function filter_coauthors_join( $join, $query ) {
	global $wpdb;

	$search = $this->get_search( $query );

	if ( ! $search ) {
		return $join;
	}

	// Since a category search is already joining the term_relationships,
	// we don't want this one to happen.
	if ( $query->is_category() ) {
		return $join;
	}

	$join .= " LEFT JOIN {$wpdb->term_relationships} ON {$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id";

	return $join;
}
				
			

Resources

Questions

Thank You

Jim Reevior

@hirozed
jim@jimreevior.com