WordPress.org

Make WordPress Core

Changeset 40136


Ignore:
Timestamp:
02/27/2017 07:53:43 PM (2 years ago)
Author:
ocean90
Message:

REST API: Fix behavior of sticky posts filter when no posts are sticky.

Previously, when getting posts from the API with sticky=true, if there were no sticky posts set, the query would return all posts as if the sticky argument was not set. In this situation, the query should return an empty array instead.

A sticky=true query that should return an empty array (in the previous situation, or with include and no intersecting post IDs) was also broken in that it would query the post with ID 1.

Finally, this commit significantly improves test coverage for the sticky filter argument, including direct testing of the WHERE clauses generated by WP_Query.

Merge of [40037] and [40122] to the 4.7 branch.

Props ryelle, jnylen0.
See #39079.
Fixes #39947.

Location:
branches/4.7
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • branches/4.7

  • branches/4.7/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    r40115 r40136  
    221221        if ( isset( $registered['sticky'], $request['sticky'] ) ) {
    222222            $sticky_posts = get_option( 'sticky_posts', array() );
    223             if ( $sticky_posts && $request['sticky'] ) {
     223            if ( ! is_array( $sticky_posts ) ) {
     224                $sticky_posts = array();
     225            }
     226            if ( $request['sticky'] ) {
    224227                /*
    225228                 * As post__in will be used to only get sticky posts,
     
    235238                 */
    236239                if ( ! $args['post__in'] ) {
    237                     $args['post__in'] = array( -1 );
     240                    $args['post__in'] = array( 0 );
    238241                }
    239242            } elseif ( $sticky_posts ) {
  • branches/4.7/tests/phpunit/tests/rest-api/rest-posts-controller.php

    r40115 r40136  
    2121
    2222    protected $forbidden_cat;
     23    protected $posts_clauses;
    2324
    2425    public static function wpSetUpBeforeClass( $factory ) {
     
    6667        parent::setUp();
    6768        register_post_type( 'youseeme', array( 'supports' => array(), 'show_in_rest' => true ) );
     69        add_filter( 'rest_pre_dispatch', array( $this, 'wpSetUpBeforeRequest' ), 10, 3 );
     70        add_filter( 'posts_clauses', array( $this, 'save_posts_clauses' ), 10, 2 );
     71    }
     72
     73    public function wpSetUpBeforeRequest( $result, $server, $request ) {
     74        $this->posts_clauses = array();
     75        return $result;
     76    }
     77
     78    public function save_posts_clauses( $orderby, $query ) {
     79        array_push( $this->posts_clauses, $orderby );
     80        return $orderby;
     81    }
     82
     83    public function assertPostsClause( $clause, $pattern ) {
     84        global $wpdb;
     85        $expected_clause = str_replace( '{posts}', $wpdb->posts, $pattern );
     86        $this->assertCount( 1, $this->posts_clauses );
     87        $this->assertEquals( $expected_clause, $this->posts_clauses[0][ $clause ] );
     88    }
     89
     90    public function assertPostsOrderedBy( $pattern ) {
     91        $this->assertPostsClause( 'orderby', $pattern );
     92    }
     93
     94    public function assertPostsWhere( $pattern ) {
     95        $this->assertPostsClause( 'where', $pattern );
    6896    }
    6997
     
    224252        $this->assertEquals( 2, count( $data ) );
    225253        $this->assertEquals( $id3, $data[0]['id'] );
     254        $this->assertPostsOrderedBy( '{posts}.post_date DESC' );
    226255        // Orderby=>include
    227256        $request->set_param( 'orderby', 'include' );
     
    230259        $this->assertEquals( 2, count( $data ) );
    231260        $this->assertEquals( $id1, $data[0]['id'] );
     261        $this->assertPostsOrderedBy( "FIELD( {posts}.ID, $id1,$id3 )" );
    232262        // Invalid include should error
    233263        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     
    438468        $data = $response->get_data();
    439469        $this->assertEquals( 'Apple Sauce', $data[0]['title']['rendered'] );
     470        $this->assertPostsOrderedBy( '{posts}.post_title DESC' );
    440471        // order=>asc
    441472        $request->set_param( 'order', 'asc' );
     
    443474        $data = $response->get_data();
    444475        $this->assertEquals( 'Apple Cobbler', $data[0]['title']['rendered'] );
     476        $this->assertPostsOrderedBy( '{posts}.post_title ASC' );
    445477        // order=>asc,id should fail
    446478        $request->set_param( 'order', 'asc,id' );
     
    480512        $this->assertEquals( $id2, $data[1]['id'] );
    481513        $this->assertEquals( $id1, $data[2]['id'] );
     514        $this->assertPostsOrderedBy( '{posts}.ID DESC' );
    482515    }
    483516
     
    496529        $this->assertEquals( 'xyz', $data[0]['slug'] );
    497530        $this->assertEquals( 'abc', $data[1]['slug'] );
     531        $this->assertPostsOrderedBy( '{posts}.post_name DESC' );
    498532    }
    499533
    500534    public function test_get_items_with_orderby_relevance() {
    501         $this->factory->post->create( array( 'post_title' => 'Title is more relevant', 'post_content' => 'Content is', 'post_status' => 'publish' ) );
    502         $this->factory->post->create( array( 'post_title' => 'Title is', 'post_content' => 'Content is less relevant', 'post_status' => 'publish' ) );
    503 
     535        $id1 = $this->factory->post->create( array( 'post_title' => 'Title is more relevant', 'post_content' => 'Content is', 'post_status' => 'publish' ) );
     536        $id2 = $this->factory->post->create( array( 'post_title' => 'Title is', 'post_content' => 'Content is less relevant', 'post_status' => 'publish' ) );
     537        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     538        $request->set_param( 'orderby', 'relevance' );
     539        $request->set_param( 'search', 'relevant' );
     540        $response = $this->server->dispatch( $request );
     541        $this->assertEquals( 200, $response->get_status() );
     542        $data = $response->get_data();
     543        $this->assertCount( 2, $data );
     544        $this->assertEquals( $id1, $data[0]['id'] );
     545        $this->assertEquals( $id2, $data[1]['id'] );
     546        $this->assertPostsOrderedBy( '{posts}.post_title LIKE \'%relevant%\' DESC, {posts}.post_date DESC' );
     547    }
     548
     549    public function test_get_items_with_orderby_relevance_two_terms() {
     550        $id1 = $this->factory->post->create( array( 'post_title' => 'Title is more relevant', 'post_content' => 'Content is', 'post_status' => 'publish' ) );
     551        $id2 = $this->factory->post->create( array( 'post_title' => 'Title is', 'post_content' => 'Content is less relevant', 'post_status' => 'publish' ) );
     552        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     553        $request->set_param( 'orderby', 'relevance' );
     554        $request->set_param( 'search', 'relevant content' );
     555        $response = $this->server->dispatch( $request );
     556        $this->assertEquals( 200, $response->get_status() );
     557        $data = $response->get_data();
     558        $this->assertCount( 2, $data );
     559        $this->assertEquals( $id1, $data[0]['id'] );
     560        $this->assertEquals( $id2, $data[1]['id'] );
     561        $this->assertPostsOrderedBy( '(CASE WHEN {posts}.post_title LIKE \'%relevant content%\' THEN 1 WHEN {posts}.post_title LIKE \'%relevant%\' AND {posts}.post_title LIKE \'%content%\' THEN 2 WHEN {posts}.post_title LIKE \'%relevant%\' OR {posts}.post_title LIKE \'%content%\' THEN 3 WHEN {posts}.post_excerpt LIKE \'%relevant content%\' THEN 4 WHEN {posts}.post_content LIKE \'%relevant content%\' THEN 5 ELSE 6 END), {posts}.post_date DESC' );
     562    }
     563
     564    public function test_get_items_with_orderby_relevance_missing_search() {
    504565        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
    505566        $request->set_param( 'orderby', 'relevance' );
     
    638699    }
    639700
    640     public function test_get_items_sticky_query() {
     701    public function test_get_items_sticky() {
    641702        $id1 = self::$post_id;
    642703        $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
     
    659720    }
    660721
    661     public function test_get_items_sticky_with_post__in_query() {
     722    public function test_get_items_sticky_with_include() {
    662723        $id1 = self::$post_id;
    663724        $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
     
    673734        $this->assertCount( 0, $response->get_data() );
    674735
     736        // FIXME Since this request returns zero posts, the query is executed twice.
     737        $this->assertCount( 2, $this->posts_clauses );
     738        $this->posts_clauses = array_slice( $this->posts_clauses, 0, 1 );
     739
     740        $this->assertPostsWhere( " AND {posts}.ID IN (0) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
     741
    675742        update_option( 'sticky_posts', array( $id1, $id2 ) );
    676743
     
    686753        $post = $posts[0];
    687754        $this->assertEquals( $id1, $post['id'] );
    688     }
    689 
    690     public function test_get_items_not_sticky_query() {
     755
     756        $this->assertPostsWhere( " AND {posts}.ID IN ($id1) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
     757    }
     758
     759    public function test_get_items_sticky_no_sticky_posts() {
     760        $id1 = self::$post_id;
     761
     762        update_option( 'sticky_posts', array() );
     763
     764        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     765        $request->set_param( 'sticky', true );
     766
     767        $response = $this->server->dispatch( $request );
     768        $this->assertCount( 0, $response->get_data() );
     769
     770        // FIXME Since this request returns zero posts, the query is executed twice.
     771        $this->assertCount( 2, $this->posts_clauses );
     772        $this->posts_clauses = array_slice( $this->posts_clauses, 0, 1 );
     773
     774        $this->assertPostsWhere( " AND {posts}.ID IN (0) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
     775    }
     776
     777    public function test_get_items_sticky_with_include_no_sticky_posts() {
     778        $id1 = self::$post_id;
     779
     780        update_option( 'sticky_posts', array() );
     781
     782        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     783        $request->set_param( 'sticky', true );
     784        $request->set_param( 'include', array( $id1 ) );
     785
     786        $response = $this->server->dispatch( $request );
     787        $this->assertCount( 0, $response->get_data() );
     788
     789        // FIXME Since this request returns zero posts, the query is executed twice.
     790        $this->assertCount( 2, $this->posts_clauses );
     791        $this->posts_clauses = array_slice( $this->posts_clauses, 0, 1 );
     792
     793        $this->assertPostsWhere( " AND {posts}.ID IN (0) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
     794    }
     795
     796    public function test_get_items_not_sticky() {
    691797        $id1 = self::$post_id;
    692798        $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
     
    703809        $post = $posts[0];
    704810        $this->assertEquals( $id1, $post['id'] );
    705     }
    706 
    707     public function test_get_items_sticky_with_post__not_in_query() {
     811
     812        $this->assertPostsWhere( " AND {posts}.ID NOT IN ($id2) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
     813    }
     814
     815    public function test_get_items_not_sticky_with_exclude() {
    708816        $id1 = self::$post_id;
    709817        $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
     
    722830        $post = $posts[0];
    723831        $this->assertEquals( $id1, $post['id'] );
     832
     833        $this->assertPostsWhere( " AND {posts}.ID NOT IN ($id3,$id2) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
     834    }
     835
     836    public function test_get_items_not_sticky_with_exclude_no_sticky_posts() {
     837        $id1 = self::$post_id;
     838        $id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
     839        $id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
     840
     841        update_option( 'sticky_posts', array() );
     842
     843        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     844        $request->set_param( 'sticky', false );
     845        $request->set_param( 'exclude', array( $id3 ) );
     846
     847        $response = $this->server->dispatch( $request );
     848        $this->assertCount( 2, $response->get_data() );
     849
     850        $posts = $response->get_data();
     851        $ids = wp_list_pluck( $posts, 'id' );
     852        sort( $ids );
     853        $this->assertEquals( array( $id1, $id2 ), $ids );
     854
     855        $this->assertPostsWhere( " AND {posts}.ID NOT IN ($id3) AND {posts}.post_type = 'post' AND (({posts}.post_status = 'publish'))" );
    724856    }
    725857
     
    29183050            $this->remove_added_uploads();
    29193051        }
     3052        remove_filter( 'rest_pre_dispatch', array( $this, 'wpSetUpBeforeRequest' ), 10, 3 );
     3053        remove_filter( 'posts_clauses', array( $this, 'save_posts_clauses' ), 10, 2 );
    29203054        parent::tearDown();
    29213055    }
Note: See TracChangeset for help on using the changeset viewer.