From 0963ea19a052127591a03c6f066a7c4dc0bc0ab8 Mon Sep 17 00:00:00 2001 From: Andrew Nacin Date: Thu, 20 Mar 2014 02:58:48 +0000 Subject: [PATCH] Fix various issues with WP_Adjacent_Post: * Performance / number of queries. * Incorrect results caused by sticky posts. * Back compat for filters, which had used "WHERE" while WP_Query does not; and fixing table references. props ethitter. fixes #26937. git-svn-id: https://develop.svn.wordpress.org/trunk@27635 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/link-template.php | 126 ++++++++++++++++++++++++------ tests/phpunit/tests/link.php | 21 +++-- 2 files changed, 119 insertions(+), 28 deletions(-) diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index a838c2a187..d58c951810 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -1243,7 +1243,7 @@ class WP_Adjacent_Post { } /** - * Allow direct access to adjacent post from the class instance itself + * Allow direct access to adjacent post from the class instance itself. * * @param string $property Property to get. * @return mixed String when adjacent post is found and post property exists. Null when no adjacent post is found. @@ -1273,7 +1273,7 @@ class WP_Adjacent_Post { * @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->current_post = get_post( $args['post'] ); $this->excluded_terms = array_map( 'intval', $args['excluded_terms'] ); $this->adjacent = $args['previous'] ? 'previous' : 'next'; $this->taxonomy = $args['taxonomy']; @@ -1291,14 +1291,20 @@ class WP_Adjacent_Post { // 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(), + 'posts_per_page' => 1, + 'post_status' => 'publish', + 'post_type' => 'post', + 'orderby' => 'date', + 'order' => 'previous' === $this->adjacent ? 'DESC' : 'ASC', + 'ignore_sticky_posts' => true, + 'date_query' => array(), + + // Performance considerations: + 'no_found_rows' => true, + 'cache_results' => true, + 'update_post_term_cache' => false, + 'update_post_meta_cache' => false, + 'split_the_query' => wp_using_ext_object_cache(), ); $tax_query = array(); @@ -1388,14 +1394,37 @@ class WP_Adjacent_Post { */ 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. + /** + * If no legacy filter callbacks are registered, proceed no further. */ - foreach ( array( 'join', 'where' ) as $clause ) { - if ( has_filter( 'get_' . $this->adjacent . '_post_' . $clause ) ) { - $clauses[ $clause ] = $this->filter_join_and_where( $clauses[ $clause ], $clause ); - } + if ( ! has_filter( 'get_' . $this->adjacent . '_post_join' ) && ! has_filter( 'get_' . $this->adjacent . '_post_where' ) && ! has_filter( 'get_' . $this->adjacent . '_post_sort' ) ) { + return $clauses; + } + + /** + * Posts table must be aliased as `p` for backwards compatibility with query previously generated by `get_adjacent_post()`. + */ + $clauses = array_map( array( $this, 'alias_posts_table' ), $clauses ); + + /** + * Apply the legacy `join` filter. + */ + if ( has_filter( 'get_' . $this->adjacent . '_post_join' ) ) { + $clauses['join'] = $this->filter_join( $clauses['join'] ); + } + + /** + * Posts table must be aliased as `p` for backwards compatibility with query previously generated by `get_adjacent_post()`. + * No filter on the table name exists, so we have to leverage the next applied filter, that for the `join` clause. + * We wait to apply this until after the legacy filter is applied so that the legacy filter doesn't remove the alias. + */ + $clauses['join'] = 'AS p ' . $clauses['join']; + + /** + * Apply the legacy `where` filter. + */ + if ( has_filter( 'get_' . $this->adjacent . '_post_where' ) ) { + $clauses['where'] = $this->filter_where( $clauses['where'] ); } /* @@ -1411,18 +1440,69 @@ class WP_Adjacent_Post { } /** - * Apply the deprecated `join` or `where` clause filter to the clauses built by WP_Query. + * Alias posts table as `p` to match query previously built by `get_adjacent_post()`. * - * @param string $value - * @param string $clause + * @global $wpdb + * @param string Clause to alias * @return string */ - protected function filter_join_and_where( $value, $clause ) { + protected function alias_posts_table( $clause ) { + global $wpdb; + + return str_replace( $wpdb->posts, 'p', $clause ); + } + + /** + * Apply the deprecated `join` clause filter to the clause built by WP_Query. + * + * @param string $join + * @return string + */ + protected function filter_join( $join ) { /** - * @todo Minimal hook docs * @deprecated 3.9.0 */ - return apply_filters( 'get_' . $this->adjacent . '_post_' . $clause, $value, $this->in_same_term, $this->excluded_terms ); + return apply_filters( 'get_' . $this->adjacent . '_post_join', $join, $this->in_same_term, $this->excluded_terms ); + } + + /** + * Apply the deprecated `where` clause filter to the clause built by WP_Query. + * + * @param string $join + * @return string + */ + protected function filter_where( $where ) { + $where = trim( $where ); + + // The legacy filter passed the entire clause, including the `WHERE`, while WP_Query's filter does not. + // We prepend the `WHERE` for the benefit of legacy callbacks that look for it. + if ( 0 !== stripos( $where, 'where' ) ) { + $where = 'WHERE 1=1 ' . $where; + } + + /** + * @deprecated 3.9.0 + */ + $where = apply_filters( 'get_' . $this->adjacent . '_post_where', $where, $this->in_same_term, $this->excluded_terms ); + + $where = trim( $where ); + + // The legacy filter passed the entire clause, including the `WHERE`, while WP_Query's filter does not. + // Removing the `WHERE` is necessary as we've added it above, and the legacy filter could include it in the returned string. + if ( 0 === stripos( $where, 'where 1=1' ) ) { + $where = substr( $where, 9 ); + } elseif ( 0 === stripos( $where, 'where' ) ) { + $where = substr( $where, 5 ); + } + + $where = trim( $where ); + + // WP_Query expects that the string returned begins with `AND`, as it is prepended with "1=1" when the clauses are joined + if ( 0 !== stripos( $where, 'and' ) ) { + $where = 'AND ' . $where; + } + + return $where; } /** diff --git a/tests/phpunit/tests/link.php b/tests/phpunit/tests/link.php index 1a29693e5e..96b60f0c8b 100644 --- a/tests/phpunit/tests/link.php +++ b/tests/phpunit/tests/link.php @@ -208,6 +208,12 @@ class Tests_Link extends WP_UnitTestCase { $this->assertEquals( $post_four, get_adjacent_post( false, null, false ) ); remove_filter( 'get_next_post_where', array( $this, 'filter_next_post_where' ) ); + // Test "where" filter that writes its own query + add_filter( 'get_previous_post_where', array( $this, 'override_previous_post_where_clause' ) ); + $this->go_to( get_permalink( $post_four->ID ) ); + $this->assertEquals( $post_two, get_adjacent_post( false, null, true ) ); + remove_filter( 'get_previous_post_where', array( $this, 'override_previous_post_where_clause' ) ); + // Test "join" filter by joining the postmeta table and restricting by meta key add_filter( 'get_next_post_join', array( $this, 'filter_next_post_join' ) ); add_filter( 'get_next_post_where', array( $this, 'filter_next_post_where_with_join' ) ); @@ -245,13 +251,21 @@ class Tests_Link extends WP_UnitTestCase { return $where; } + /** + * Filter callback for `test_legacy_get_adjacent_post_filters()` + */ + function override_previous_post_where_clause( $where ) { + $where = "WHERE p.post_date < '2012-02-28'"; + return $where; + } + /** * Filter callback for `test_legacy_get_adjacent_post_filters()` */ function filter_next_post_join( $join ) { global $wpdb; - $join .= " INNER JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id"; + $join .= " INNER JOIN {$wpdb->postmeta} ON p.ID = {$wpdb->postmeta}.post_id"; return $join; } @@ -269,10 +283,7 @@ class Tests_Link extends WP_UnitTestCase { * Filter callback for `test_legacy_get_adjacent_post_filters()` */ function filter_next_post_sort( $sort ) { - global $wpdb; - - $sort = str_replace( $wpdb->posts . '.post_date', $wpdb->posts . '.post_title', $sort ); - return $sort; + return str_replace( 'p.post_date', 'p.post_title', $sort ); } /**