Commit 33b365fb authored by Andrew Nacin's avatar Andrew Nacin
Browse files

Make get_adjacent_post() wrap a new WP_Get_Adjacent_Post object that uses WP_Query.

Built from https://develop.svn.wordpress.org/trunk@27285


git-svn-id: https://core.svn.wordpress.org/trunk@27141 1a063a9b-81f0-0310-95a4-ce76da25c4cd
parent 4509f21d
Loading
Loading
Loading
Loading
+256 −55
Original line number Diff line number Diff line
@@ -1119,82 +1119,283 @@ function get_next_post( $in_same_term = false, $excluded_terms = '', $taxonomy =
 * @param array|string $excluded_terms Optional. Array or comma-separated list of excluded term IDs.
 * @param bool         $previous       Optional. Whether to retrieve previous post.
 * @param string       $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
 * @return mixed       Post object if successful. Null if global $post is not set. Empty string if no corresponding post exists.
 * @return mixed       Post object if successful. Null if current post doesn't exist. Empty string if no corresponding adjacent post exists.
 */
function get_adjacent_post( $in_same_term = false, $excluded_terms = '', $previous = true, $taxonomy = 'category' ) {
	global $wpdb;
	if ( is_string( $excluded_terms ) && false !== strpos( $excluded_terms, ' and ' ) ) {
		// back-compat: $excluded_terms used to be IDs separated by " and "
		_deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) );
		$excluded_terms = explode( ' and ', $excluded_terms );
	}
	if ( $excluded_terms ) {
		$excluded_terms = wp_parse_id_list( $excluded_terms );
	} else {
		$excluded_terms = array();
	}

	if ( ( ! $post = get_post() ) || ! taxonomy_exists( $taxonomy ) )
		return null;
	$adjacent = new WP_Get_Adjacent_Post( array(
		'post'           => get_post(),
		'previous'       => $previous,
		'taxonomy'       => $taxonomy,
		'in_same_term'   => $in_same_term,
		'excluded_terms' => $excluded_terms,
	) );

	$current_post_date = $post->post_date;
	return $adjacent->adjacent_post;
}

	$join = '';
	$posts_in_ex_terms_sql = '';
	if ( $in_same_term || ! empty( $excluded_terms ) ) {
		$join = " INNER JOIN $wpdb->term_relationships AS tr ON p.ID = tr.object_id INNER JOIN $wpdb->term_taxonomy tt ON tr.term_taxonomy_id = tt.term_taxonomy_id";
/**
 * WordPress Adjacent Post API
 *
 * Based on the current or specified post, determines either the previous or
 * next post based on the criteria specified. Supports retrieving posts with the
 * same taxonomy terms and posts that lack specific terms.
 */
class WP_Get_Adjacent_Post {
	public $adjacent_post = null;

		if ( $in_same_term ) {
			if ( ! is_object_in_taxonomy( $post->post_type, $taxonomy ) )
				return '';
			$term_array = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'ids' ) );
			if ( ! $term_array || is_wp_error( $term_array ) )
				return '';
			$join .= $wpdb->prepare( " AND tt.taxonomy = %s AND tt.term_id IN (" . implode( ',', array_map( 'intval', $term_array ) ) . ")", $taxonomy );
	protected $current_post   = false;
	protected $adjacent       = 'previous';
	protected $taxonomy       = 'category';
	protected $in_same_term   = false;
	protected $excluded_terms = '';

	/**
	 * Class constructor.
	 *
	 * The post is queried is run if arguments are passed to the constructor.
	 * Otherwise, the get_post() method will need to be called.
	 *
	 * @param array $args Optional. See the get_post() method for $args.
	 */
	public function __construct( $args = array() ) {
		if ( empty( $args ) ) {
			return;
		}

		$posts_in_ex_terms_sql = $wpdb->prepare( "AND tt.taxonomy = %s", $taxonomy );
		if ( ! empty( $excluded_terms ) ) {
			if ( ! is_array( $excluded_terms ) ) {
				// back-compat, $excluded_terms used to be $excluded_terms with IDs separated by " and "
				if ( false !== strpos( $excluded_terms, ' and ' ) ) {
					_deprecated_argument( __FUNCTION__, '3.3', sprintf( __( 'Use commas instead of %s to separate excluded terms.' ), "'and'" ) );
					$excluded_terms = explode( ' and ', $excluded_terms );
		$this->get_post( $args );
	}

	/**
	 * Allow direct access to adjacent post from the class instance itself
	 *
	 * @param string $property
	 * @return mixed String when adjacent post is found and post property exists. Null when no adjacent post is found.
	 */
	public function __get( $property ) {
		if ( is_object( $this->adjacent_post ) && property_exists( $this->adjacent_post, $property ) ) {
			return $this->adjacent_post->{$property};
		} else {
					$excluded_terms = explode( ',', $excluded_terms );
			return null;
		}
	}

			$excluded_terms = array_map( 'intval', $excluded_terms );
	/**
	 * Determine adjacent post for specified post and adjacency.
	 *
	 * @since 3.9.0
	 *
	 * @param array $args {
	 *     Arguments for querying the adjacent post.
	 *
	 *     @type mixed  $post           Optional. Post object or ID to find adjacent post for.
	 *     @type bool   $previous       Optional. Whether to retrieve previous post.
	 *     @type string $taxonomy       Optional. Taxonomy, if $in_same_term is true. Default 'category'.
	 *     @type bool   $in_same_term   Optional. Whether post should be in a same taxonomy term.
	 *     @type array  $excluded_terms Optional. Array of excluded term IDs.
	 * }
	 * @return mixed Post object on success. False if no adjacent post exists. Null on failure.
	 */
	protected function get_post( $args ) {
		$this->current_post = get_post( $args['post'] );
		$this->excluded_terms = array_map( 'intval', $args['excluded_terms'] );
		$this->adjacent       = $args['previous'] ? 'previous' : 'next';
		$this->in_same_term   = (bool) $args['in_same_term'];

			if ( ! empty( $term_array ) ) {
				$excluded_terms = array_diff( $excluded_terms, $term_array );
				$posts_in_ex_terms_sql = '';
		// Return null when either the post or taxonomy doesn't exist.
		if ( ! $this->current_post ) {
			return;
		}
		if ( $this->in_same_term || $this->excluded_terms ) {
			if ( ! taxonomy_exists( $args['taxonomy'] ) ) {
				return;
			}
		}

			if ( ! empty( $excluded_terms ) ) {
				$posts_in_ex_terms_sql = $wpdb->prepare( " AND tt.taxonomy = %s AND tt.term_id NOT IN (" . implode( $excluded_terms, ',' ) . ')', $taxonomy );
		// Build our arguments for WP_Query.
		$query_args = array(
			'posts_per_page'   => 1,
			'post_status'      => 'publish',
			'post_type'        => 'post',
			'orderby'          => 'date',
			'order'            => 'previous' === $this->adjacent ? 'DESC' : 'ASC',
			'no_found_rows'    => true,
			'cache_results'    => true,
			'date_query'       => array(),
		);

		$tax_query = array();

		// Set up for requests limited to posts that share terms.
		if ( $this->in_same_term ) {
			$terms = get_the_terms( $this->current_post->ID, $args['taxonomy'] );

			if ( is_array( $terms ) && ! empty( $terms ) ) {
				$terms = wp_list_pluck( $terms, 'term_id' );
				$terms = array_values( $terms );
				$terms = array_map( 'intval', $terms );
			} else {
				unset( $terms );
			}
		}

		// Handle excluded terms.
		if ( $this->excluded_terms ) {
			$tax_query[] = array(
				'taxonomy' => $args['taxonomy'],
				'slugs'    => $this->excluded_terms,
				'compare'  => 'NOT IN',
			);
		}

	$adjacent = $previous ? 'previous' : 'next';
	$op = $previous ? '<' : '>';
	$order = $previous ? 'DESC' : 'ASC';

	$join  = apply_filters( "get_{$adjacent}_post_join", $join, $in_same_term, $excluded_terms );
	$where = apply_filters( "get_{$adjacent}_post_where", $wpdb->prepare( "WHERE p.post_date $op %s AND p.post_type = %s AND p.post_status = 'publish' $posts_in_ex_terms_sql", $current_post_date, $post->post_type), $in_same_term, $excluded_terms );
	$sort  = apply_filters( "get_{$adjacent}_post_sort", "ORDER BY p.post_date $order LIMIT 1" );

	$query = "SELECT p.ID FROM $wpdb->posts AS p $join $where $sort";
	$query_key = 'adjacent_post_' . md5( $query );
	$result = wp_cache_get( $query_key, 'counts' );
	if ( false !== $result ) {
		if ( $result )
			$result = get_post( $result );
		return $result;
		// If requesting same term, ensure excluded terms don't appear in term list.
		if ( isset( $terms ) ) {
			if ( isset( $this->excluded_terms ) && is_array( $this->excluded_terms ) ) {
				$terms = array_diff( $terms, $this->excluded_terms );
			}

			if ( ! empty( $terms ) ) {
				$tax_query[] = array(
					'taxonomy' => $args['taxonomy'],
					'terms'    => $terms,
				);
			}
		}

	$result = $wpdb->get_var( $query );
	if ( null === $result )
		$result = '';
		// If we have a tax query, add it to our query args.
		if ( $tax_query ) {
			$query_args['tax_query'] = $tax_query;
		}

		// And now, the date constraint.
		$date_query_key = 'previous' === $this->adjacent ? 'before' : 'after';

	wp_cache_set( $query_key, $result, 'counts' );
		$query_args['date_query'][] = array(
			$date_query_key => $this->current_post->post_date,
			'inclusive'     => true,
		);

	if ( $result )
		$result = get_post( $result );
		// Ensure the current post isn't returned, since we're using an inclusive date query.
		$query_args['post__not_in'] = array( $this->current_post->ID );

	return $result;
		/**
		 * Filter the arguments passed to WP_Query when finding an adjacent post.
		 *
		 * @since 3.9.0
		 *
		 * @param array $query_args WP_Query arguments.
		 * @param array $args       Arguments passed to WP_Get_Adjacent_Post.
		 */
		$query_args = apply_filters( 'get_adjacent_post_query_args', $query_args, $args );

		add_filter( 'posts_clauses', array( $this, 'filter' ) );
		$query = new WP_Query( $query_args );

		if ( $query->posts ) {
			$this->adjacent_post = $query->post;
		} else {
			$this->adjacent_post = false;
		}
	}

	/**
	 * Apply the deprecated filters to WP_Query's clauses.
	 *
	 * @param array $clauses
	 * @uses $this->filter_join_and_where()
	 * @uses $this->filter_sort()
	 * @filter post_clauses
	 * @return array
	 */
	public function filter( $clauses ) {
		// Immediately deregister these legacy filters to avoid modifying
		// any calls to WP_Query from filter callbacks hooked to WP_Query filters.
		remove_filter( 'posts_clauses', array( $this, 'filter' ) );

		// The `join` and `where` filters are identical in their parameters,
		// so we can use the same approach for both.
		foreach ( array( 'join', 'where' ) as $clause ) {
			if ( has_filter( 'get_' . $this->adjacent . '_post_' . $clause ) ) {
				$clauses[ $clause ] = $this->filter_join_and_where( $clauses[ $clause ], $clause );
			}
		}

		// The legacy `sort` filter combined the ORDER BY and LIMIT clauses,
		// while `WP_Query` does not, which requires special handling.
		if ( has_filter( 'get_' . $this->adjacent . '_post_sort' ) ) {
			$sort_clauses = $this->filter_sort( $clauses['orderby'], $clauses['limits'] );
			$clauses      = array_merge( $clauses, $sort_clauses );
		}

		return $clauses;
	}

	/**
	 * Apply the deprecated `join` or `where` clause filter to the clauses built by WP_Query.
	 *
	 * @param string $value
	 * @param string $clause
	 * @return string
	 */
	protected function filter_join_and_where( $value, $clause ) {
		/**
		 * @deprecated 3.9.0
		 */
		return apply_filters( 'get_' . $this->adjacent . '_post_' . $clause, $value, $this->in_same_term, $this->excluded_terms );
	}

	/**
	 * Apply deprecated `sort` filter, which applies to both the ORDER BY and LIMIT clauses.
	 *
	 * @param string $orderby
	 * @param string $limits
	 * @return array
	 */
	protected function filter_sort( $orderby, $limits ) {
		/**
		 * @deprecated 3.9.0
		 */
		$sort = apply_filters( 'get_' . $this->adjacent . '_post_sort', 'ORDER BY ' . $orderby . ' ' . $limits );

		if ( empty( $sort ) ) {
			return compact( 'orderby', 'limits' );
		}

		// The legacy filter could allow either clause to be removed, or their order inverted, so we need to know what we have and where.
		$has_order_by = stripos( $sort, 'order by' );
		$has_limit    = stripos( $sort, 'limit' );

		// Split the string of one or two clauses into their respective array keys
		if ( false !== $has_order_by && false !== $has_limit ) {
			// The LIMIT clause cannot appear before the ORDER BY clause in a valid query
			// However, since the legacy filter would allow a user to invert the order, we maintain that handling so the same errors are triggered.
			if ( $has_order_by < $has_limit ) {
				$orderby = trim( str_ireplace( 'order by', '', substr( $sort, 0, $has_limit ) ) );
				$limits  = trim( substr( $sort, $has_limit ) );
			} else {
				$orderby = trim( str_ireplace( 'order by', '', substr( $sort, $has_order_by ) ) );
				$limits  = trim( substr( $sort, 0, $has_order_by ) );
			}
		} elseif ( false !== $has_order_by ) {
			$orderby = trim( str_ireplace( 'order by', '', $sort ) );
			$limits  = '';
		} elseif ( false !== $has_limit ) {
			$orderby = '';
			$limits  = trim( $sort );
		}

		return compact( 'orderby', 'limits' );
	}
}

/**