Make WordPress Core

Changeset 59115


Ignore:
Timestamp:
09/30/2024 01:17:40 AM (2 weeks ago)
Author:
peterwilsoncc
Message:

REST API/Editor: Support post formats in Query Block & Posts API.

Introduces post format support for both the Query Block with the new parameter format. In the build_query_vars_from_query_block() function, this is converted to a post_format taxonomy query passed to WP_Query.

Also introduces the format parameter to the REST API's Posts controller to support the feature in the Query block. The parameter type is an enumerated string accepted the post formats supported by each post type.

Props poena, mukesh27, mamaduka, noisysocks, TimothyBlynJacobs.
Fixes #62014.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r59101 r59115  
    23362336 * @since 5.8.0
    23372337 * @since 6.1.0 Added `query_loop_block_query_vars` filter and `parents` support in query.
     2338 * @since 6.7.0 Added support for the `format` property in query.
    23382339 *
    23392340 * @param WP_Block $block Block instance.
     
    23482349        'orderby'      => 'date',
    23492350        'post__not_in' => array(),
     2351        'tax_query'    => array(),
    23502352    );
    23512353
     
    23972399        // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
    23982400        if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
    2399             $tax_query = array();
     2401            $tax_query_back_compat = array();
    24002402            if ( ! empty( $block->context['query']['categoryIds'] ) ) {
    2401                 $tax_query[] = array(
     2403                $tax_query_back_compat[] = array(
    24022404                    'taxonomy'         => 'category',
    24032405                    'terms'            => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
     
    24062408            }
    24072409            if ( ! empty( $block->context['query']['tagIds'] ) ) {
    2408                 $tax_query[] = array(
     2410                $tax_query_back_compat[] = array(
    24092411                    'taxonomy'         => 'post_tag',
    24102412                    'terms'            => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
     
    24122414                );
    24132415            }
    2414             $query['tax_query'] = $tax_query;
     2416            $query['tax_query'] = array_merge( $query['tax_query'], $tax_query_back_compat );
    24152417        }
    24162418        if ( ! empty( $block->context['query']['taxQuery'] ) ) {
    2417             $query['tax_query'] = array();
     2419            $tax_query = array();
    24182420            foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
    24192421                if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
    2420                     $query['tax_query'][] = array(
     2422                    $tax_query[] = array(
    24212423                        'taxonomy'         => $taxonomy,
    24222424                        'terms'            => array_filter( array_map( 'intval', $terms ) ),
     
    24252427                }
    24262428            }
    2427         }
     2429            $query['tax_query'] = array_merge( $query['tax_query'], $tax_query );
     2430        }
     2431        if ( ! empty( $block->context['query']['format'] ) && is_array( $block->context['query']['format'] ) ) {
     2432            $formats = $block->context['query']['format'];
     2433            /*
     2434             * Validate that the format is either `standard` or a supported post format.
     2435             * - First, add `standard` to the array of valid formats.
     2436             * - Then, remove any invalid formats.
     2437             */
     2438            $valid_formats = array_merge( array( 'standard' ), get_post_format_slugs() );
     2439            $formats       = array_intersect( $formats, $valid_formats );
     2440
     2441            /*
     2442             * The relation needs to be set to `OR` since the request can contain
     2443             * two separate conditions. The user may be querying for items that have
     2444             * either the `standard` format or a specific format.
     2445             */
     2446            $formats_query = array( 'relation' => 'OR' );
     2447
     2448            /*
     2449             * The default post format, `standard`, is not stored in the database.
     2450             * If `standard` is part of the request, the query needs to exclude all post items that
     2451             * have a format assigned.
     2452             */
     2453            if ( in_array( 'standard', $formats, true ) ) {
     2454                $formats_query[] = array(
     2455                    'taxonomy' => 'post_format',
     2456                    'field'    => 'slug',
     2457                    'operator' => 'NOT EXISTS',
     2458                );
     2459                // Remove the `standard` format, since it cannot be queried.
     2460                unset( $formats[ array_search( 'standard', $formats, true ) ] );
     2461            }
     2462            // Add any remaining formats to the formats query.
     2463            if ( ! empty( $formats ) ) {
     2464                // Add the `post-format-` prefix.
     2465                $terms = array_map(
     2466                    static function ( $format ) {
     2467                        return "post-format-$format";
     2468                    },
     2469                    $formats
     2470                );
     2471                $formats_query[] = array(
     2472                    'taxonomy' => 'post_format',
     2473                    'field'    => 'slug',
     2474                    'terms'    => $terms,
     2475                    'operator' => 'IN',
     2476                );
     2477            }
     2478
     2479            /*
     2480             * Add `$formats_query` to `$query`, as long as it contains more than one key:
     2481             * If `$formats_query` only contains the initial `relation` key, there are no valid formats to query,
     2482             * and the query should not be modified.
     2483             */
     2484            if ( count( $formats_query ) > 1 ) {
     2485                // Enable filtering by both post formats and other taxonomies by combining them with `AND`.
     2486                if ( empty( $query['tax_query'] ) ) {
     2487                    $query['tax_query'] = $formats_query;
     2488                } else {
     2489                    $query['tax_query'] = array(
     2490                        'relation' => 'AND',
     2491                        $query['tax_query'],
     2492                        $formats_query,
     2493                    );
     2494                }
     2495            }
     2496        }
     2497
    24282498        if (
    24292499            isset( $block->context['query']['order'] ) &&
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    r59064 r59115  
    346346
    347347        $args = $this->prepare_tax_query( $args, $request );
     348
     349        if ( ! empty( $request['format'] ) ) {
     350            $formats = $request['format'];
     351            /*
     352             * The relation needs to be set to `OR` since the request can contain
     353             * two separate conditions. The user may be querying for items that have
     354             * either the `standard` format or a specific format.
     355             */
     356            $formats_query = array( 'relation' => 'OR' );
     357
     358            /*
     359             * The default post format, `standard`, is not stored in the database.
     360             * If `standard` is part of the request, the query needs to exclude all post items that
     361             * have a format assigned.
     362             */
     363            if ( in_array( 'standard', $formats, true ) ) {
     364                $formats_query[] = array(
     365                    'taxonomy' => 'post_format',
     366                    'field'    => 'slug',
     367                    'operator' => 'NOT EXISTS',
     368                );
     369                // Remove the `standard` format, since it cannot be queried.
     370                unset( $formats[ array_search( 'standard', $formats, true ) ] );
     371            }
     372
     373            // Add any remaining formats to the formats query.
     374            if ( ! empty( $formats ) ) {
     375                // Add the `post-format-` prefix.
     376                $terms = array_map(
     377                    static function ( $format ) {
     378                        return "post-format-$format";
     379                    },
     380                    $formats
     381                );
     382
     383                $formats_query[] = array(
     384                    'taxonomy' => 'post_format',
     385                    'field'    => 'slug',
     386                    'terms'    => $terms,
     387                    'operator' => 'IN',
     388                );
     389            }
     390
     391            // Enable filtering by both post formats and other taxonomies by combining them with `AND`.
     392            if ( isset( $args['tax_query'] ) ) {
     393                $args['tax_query'][] = array(
     394                    'relation' => 'AND',
     395                    $formats_query,
     396                );
     397            } else {
     398                $args['tax_query'] = $formats_query;
     399            }
     400        }
    348401
    349402        // Force the post_type argument, since it's not a user input variable.
     
    29933046        }
    29943047
     3048        if ( post_type_supports( $this->post_type, 'post-formats' ) ) {
     3049            $query_params['format'] = array(
     3050                'description' => __( 'Limit result set to items assigned one or more given formats.' ),
     3051                'type'        => 'array',
     3052                'uniqueItems' => true,
     3053                'items'       => array(
     3054                    'enum' => array_values( get_post_format_slugs() ),
     3055                    'type' => 'string',
     3056                ),
     3057            );
     3058        }
     3059
    29953060        /**
    29963061         * Filters collection parameters for the posts controller.
  • trunk/tests/phpunit/tests/blocks/wpBlock.php

    r58160 r59115  
    465465
    466466    /**
     467     * @ticket 62014
     468     */
     469    public function test_build_query_vars_from_query_block_standard_post_formats() {
     470        $this->registry->register(
     471            'core/example',
     472            array( 'uses_context' => array( 'query' ) )
     473        );
     474
     475        $parsed_blocks = parse_blocks( '<!-- wp:example {"ok":true} -->a<!-- wp:example /-->b<!-- /wp:example -->' );
     476        $parsed_block  = $parsed_blocks[0];
     477        $context       = array(
     478            'query' => array(
     479                'postType' => 'post',
     480                'format'   => array( 'standard' ),
     481            ),
     482        );
     483        $block         = new WP_Block( $parsed_block, $context, $this->registry );
     484        $query         = build_query_vars_from_query_block( $block, 1 );
     485
     486        $this->assertSame(
     487            array(
     488                'post_type'    => 'post',
     489                'order'        => 'DESC',
     490                'orderby'      => 'date',
     491                'post__not_in' => array(),
     492                'tax_query'    => array(
     493                    'relation' => 'OR',
     494                    array(
     495                        'taxonomy' => 'post_format',
     496                        'field'    => 'slug',
     497                        'operator' => 'NOT EXISTS',
     498                    ),
     499                ),
     500            ),
     501            $query
     502        );
     503    }
     504
     505    /**
     506     * @ticket 62014
     507     */
     508    public function test_build_query_vars_from_query_block_post_format() {
     509        $this->registry->register(
     510            'core/example',
     511            array( 'uses_context' => array( 'query' ) )
     512        );
     513
     514        $parsed_blocks = parse_blocks( '<!-- wp:example {"ok":true} -->a<!-- wp:example /-->b<!-- /wp:example -->' );
     515        $parsed_block  = $parsed_blocks[0];
     516        $context       = array(
     517            'query' => array(
     518                'postType' => 'post',
     519                'format'   => array( 'aside' ),
     520            ),
     521        );
     522        $block         = new WP_Block( $parsed_block, $context, $this->registry );
     523        $query         = build_query_vars_from_query_block( $block, 1 );
     524
     525        $this->assertSame(
     526            array(
     527                'post_type'    => 'post',
     528                'order'        => 'DESC',
     529                'orderby'      => 'date',
     530                'post__not_in' => array(),
     531                'tax_query'    => array(
     532                    'relation' => 'OR',
     533                    array(
     534                        'taxonomy' => 'post_format',
     535                        'field'    => 'slug',
     536                        'terms'    => array( 'post-format-aside' ),
     537                        'operator' => 'IN',
     538                    ),
     539                ),
     540            ),
     541            $query
     542        );
     543    }
     544    /**
     545     * @ticket 62014
     546     */
     547    public function test_build_query_vars_from_query_block_post_formats_with_category() {
     548        $this->registry->register(
     549            'core/example',
     550            array( 'uses_context' => array( 'query' ) )
     551        );
     552
     553        $parsed_blocks = parse_blocks( '<!-- wp:example {"ok":true} -->a<!-- wp:example /-->b<!-- /wp:example -->' );
     554        $parsed_block  = $parsed_blocks[0];
     555        $context       = array(
     556            'query' => array(
     557                'postType'    => 'post',
     558                'format'      => array( 'standard' ),
     559                'categoryIds' => array( 56 ),
     560            ),
     561        );
     562        $block         = new WP_Block( $parsed_block, $context, $this->registry );
     563        $query         = build_query_vars_from_query_block( $block, 1 );
     564
     565        $this->assertSame(
     566            array(
     567                'post_type'    => 'post',
     568                'order'        => 'DESC',
     569                'orderby'      => 'date',
     570                'post__not_in' => array(),
     571                'tax_query'    => array(
     572                    'relation' => 'AND',
     573                    array(
     574                        array(
     575                            'taxonomy'         => 'category',
     576                            'terms'            => array( 56 ),
     577                            'include_children' => false,
     578                        ),
     579                    ),
     580                    array(
     581                        'relation' => 'OR',
     582                        array(
     583                            'taxonomy' => 'post_format',
     584                            'field'    => 'slug',
     585                            'operator' => 'NOT EXISTS',
     586                        ),
     587                    ),
     588                ),
     589            ),
     590            $query
     591        );
     592    }
     593
     594    /**
    467595     * @ticket 52991
    468596     */
     
    482610                'orderby'      => 'date',
    483611                'post__not_in' => array(),
     612                'tax_query'    => array(),
    484613            )
    485614        );
     
    513642                'orderby'        => 'date',
    514643                'post__not_in'   => array(),
     644                'tax_query'      => array(),
    515645                'offset'         => 0,
    516646                'posts_per_page' => 2,
     
    545675                'orderby'        => 'date',
    546676                'post__not_in'   => array(),
     677                'tax_query'      => array(),
    547678                'offset'         => 10,
    548679                'posts_per_page' => 5,
     
    577708                'orderby'        => 'date',
    578709                'post__not_in'   => array(),
     710                'tax_query'      => array(),
    579711                'offset'         => 12,
    580712                'posts_per_page' => 5,
     
    620752                'orderby'      => 'title',
    621753                'post__not_in' => array(),
     754                'tax_query'    => array(),
    622755            )
    623756        );
  • trunk/tests/phpunit/tests/rest-api/rest-posts-controller.php

    r59036 r59115  
    197197                'context',
    198198                'exclude',
     199                'format',
    199200                'include',
    200201                'modified_after',
     
    54985499
    54995500    /**
     5501     * Test the REST API support for the standard post format.
     5502     *
     5503     * @ticket 62014
     5504     *
     5505     * @covers WP_REST_Posts_Controller::get_items
     5506     */
     5507    public function test_standard_post_format_support() {
     5508        $initial_theme_support = get_theme_support( 'post-formats' );
     5509        add_theme_support( 'post-formats', array( 'aside', 'gallery', 'link', 'image', 'quote', 'status', 'video', 'audio', 'chat' ) );
     5510
     5511        $post_id = self::factory()->post->create(
     5512            array(
     5513                'post_type'   => 'post',
     5514                'post_status' => 'publish',
     5515            )
     5516        );
     5517        set_post_format( $post_id, 'aside' );
     5518
     5519        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     5520        $request->set_param( 'format', array( 'standard' ) );
     5521        $request->set_param( 'per_page', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
     5522
     5523        $response = rest_get_server()->dispatch( $request );
     5524
     5525        /*
     5526         * Restore the initial post formats support.
     5527         *
     5528         * This needs to be done prior to the assertions to avoid unexpected
     5529         * results for other tests should an assertion fail.
     5530         */
     5531        if ( $initial_theme_support ) {
     5532            add_theme_support( 'post-formats', $initial_theme_support[0] );
     5533        } else {
     5534            remove_theme_support( 'post-formats' );
     5535        }
     5536
     5537        $this->assertCount( 3, $response->get_data(), 'The response should only include standard post formats' );
     5538    }
     5539
     5540    /**
     5541     * Test the REST API support for post formats.
     5542     *
     5543     * @ticket 62014
     5544     *
     5545     * @covers WP_REST_Posts_Controller::get_items
     5546     */
     5547    public function test_post_format_support() {
     5548        $initial_theme_support = get_theme_support( 'post-formats' );
     5549        add_theme_support( 'post-formats', array( 'aside', 'gallery', 'link', 'image', 'quote', 'status', 'video', 'audio', 'chat' ) );
     5550
     5551        $post_id = self::factory()->post->create(
     5552            array(
     5553                'post_type'   => 'post',
     5554                'post_status' => 'publish',
     5555            )
     5556        );
     5557        set_post_format( $post_id, 'aside' );
     5558
     5559        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     5560        $request->set_param( 'format', array( 'aside' ) );
     5561
     5562        $response_aside = rest_get_server()->dispatch( $request );
     5563
     5564        $request->set_param( 'format', array( 'invalid_format' ) );
     5565        $response_invalid = rest_get_server()->dispatch( $request );
     5566
     5567        /*
     5568         * Restore the initial post formats support.
     5569         *
     5570         * This needs to be done prior to the assertions to avoid unexpected
     5571         * results for other tests should an assertion fail.
     5572         */
     5573        if ( $initial_theme_support ) {
     5574            add_theme_support( 'post-formats', $initial_theme_support[0] );
     5575        } else {
     5576            remove_theme_support( 'post-formats' );
     5577        }
     5578
     5579        $this->assertCount( 1, $response_aside->get_data(), 'Only one post is expected to be returned.' );
     5580        $this->assertErrorResponse( 'rest_invalid_param', $response_invalid, 400, 'An invalid post format should return an error' );
     5581    }
     5582
     5583    /**
     5584     * Test the REST API support for multiple post formats.
     5585     *
     5586     * @ticket 62014
     5587     *
     5588     * @covers WP_REST_Posts_Controller::get_items
     5589     */
     5590    public function test_multiple_post_format_support() {
     5591        $initial_theme_support = get_theme_support( 'post-formats' );
     5592        add_theme_support( 'post-formats', array( 'aside', 'gallery', 'link', 'image', 'quote', 'status', 'video', 'audio', 'chat' ) );
     5593
     5594        $post_id = self::factory()->post->create(
     5595            array(
     5596                'post_type'   => 'post',
     5597                'post_status' => 'publish',
     5598            )
     5599        );
     5600        set_post_format( $post_id, 'aside' );
     5601
     5602        $post_id_2 = self::factory()->post->create(
     5603            array(
     5604                'post_type'   => 'post',
     5605                'post_status' => 'publish',
     5606            )
     5607        );
     5608        set_post_format( $post_id_2, 'gallery' );
     5609
     5610        $request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
     5611        $request->set_param( 'format', array( 'aside', 'gallery' ) );
     5612
     5613        $response = rest_get_server()->dispatch( $request );
     5614
     5615        /*
     5616         * Restore the initial post formats support.
     5617         *
     5618         * This needs to be done prior to the assertions to avoid unexpected
     5619         * results for other tests should an assertion fail.
     5620         */
     5621        if ( $initial_theme_support ) {
     5622            add_theme_support( 'post-formats', $initial_theme_support[0] );
     5623        } else {
     5624            remove_theme_support( 'post-formats' );
     5625        }
     5626
     5627        $this->assertCount( 2, $response->get_data(), 'Two posts are expected to be returned' );
     5628    }
     5629
     5630    /**
    55005631     * Internal function used to disable an insert query which
    55015632     * will trigger a wpdb error for testing purposes.
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r59034 r59115  
    626626                            "description": "Limit result set to items that are sticky.",
    627627                            "type": "boolean",
     628                            "required": false
     629                        },
     630                        "format": {
     631                            "description": "Limit result set to items assigned one or more given formats.",
     632                            "type": "array",
     633                            "uniqueItems": true,
     634                            "items": {
     635                                "enum": [
     636                                    "standard",
     637                                    "aside",
     638                                    "chat",
     639                                    "gallery",
     640                                    "link",
     641                                    "image",
     642                                    "quote",
     643                                    "status",
     644                                    "video",
     645                                    "audio"
     646                                ],
     647                                "type": "string"
     648                            },
    628649                            "required": false
    629650                        }
Note: See TracChangeset for help on using the changeset viewer.