Make WordPress Core

Changeset 53152


Ignore:
Timestamp:
04/12/2022 09:24:51 AM (2 years ago)
Author:
gziolo
Message:

REST API: Bring new endpoints for Block Patterns from Gutenberg plugin

Related Gutenberg issue: https://github.com/WordPress/gutenberg/issues/39889.

Backporting changes from the Gutenberg plugin:

  • new Block Patterns REST API endpoint
  • new Block Pattern Categories REST API endpoint
  • updates to Query Loop related patterns
  • support for custom taxonomies in Query Loop block

Props hellofromtonya, peterwilsoncc, ntsekouras, zieladam, ironprogrammer, spacedmonkey, timothyblynjacobs, antonvlasenko, jsnajdr.
See #55505.

Location:
trunk
Files:
4 added
15 edited

Legend:

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

    r52597 r53152  
    1313 *
    1414 * @since 5.5.0
    15  * @private
     15 * @access private
    1616 */
    1717function _register_core_block_patterns_and_categories() {
     
    128128    }
    129129}
     130
     131/**
     132 * Registers patterns from Pattern Directory provided by a theme's
     133 * `theme.json` file.
     134 *
     135 * @since 6.0.0
     136 * @access private
     137 */
     138function _register_remote_theme_patterns() {
     139    if ( ! get_theme_support( 'core-block-patterns' ) ) {
     140        return;
     141    }
     142
     143    /** This filter is documented in wp-includes/block-patterns.php */
     144    if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) {
     145        return;
     146    }
     147
     148    if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
     149        return;
     150    }
     151
     152    $pattern_settings = WP_Theme_JSON_Resolver::get_theme_data()->get_patterns();
     153    if ( empty( $pattern_settings ) ) {
     154        return;
     155    }
     156
     157    $request         = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' );
     158    $request['slug'] = implode( ',', $pattern_settings );
     159    $response        = rest_do_request( $request );
     160    if ( $response->is_error() ) {
     161        return;
     162    }
     163    $patterns          = $response->get_data();
     164    $patterns_registry = WP_Block_Patterns_Registry::get_instance();
     165    foreach ( $patterns as $pattern ) {
     166        $pattern_name = sanitize_title( $pattern['title'] );
     167        // Some patterns might be already registered as core patterns with the `core` prefix.
     168        $is_registered = $patterns_registry->is_registered( $pattern_name ) || $patterns_registry->is_registered( "core/$pattern_name" );
     169        if ( ! $is_registered ) {
     170            register_block_pattern( $pattern_name, (array) $pattern );
     171        }
     172    }
     173}
     174
     175/**
     176 * Register any patterns that the active theme may provide under its
     177 * `./patterns/` directory. Each pattern is defined as a PHP file and defines
     178 * its metadata using plugin-style headers. The minimum required definition is:
     179 *
     180 *     /**
     181 *      * Title: My Pattern
     182 *      * Slug: my-theme/my-pattern
     183 *      *
     184 *
     185 * The output of the PHP source corresponds to the content of the pattern, e.g.:
     186 *
     187 *     <main><p><?php echo "Hello"; ?></p></main>
     188 *
     189 * If applicable, this will collect from both parent and child theme.
     190 *
     191 * Other settable fields include:
     192 *
     193 *   - Description
     194 *   - Viewport Width
     195 *   - Categories       (comma-separated values)
     196 *   - Keywords         (comma-separated values)
     197 *   - Block Types      (comma-separated values)
     198 *   - Inserter         (yes/no)
     199 *
     200 * @since 6.0.0
     201 * @access private
     202 */
     203function _register_theme_block_patterns() {
     204    $default_headers = array(
     205        'title'         => 'Title',
     206        'slug'          => 'Slug',
     207        'description'   => 'Description',
     208        'viewportWidth' => 'Viewport Width',
     209        'categories'    => 'Categories',
     210        'keywords'      => 'Keywords',
     211        'blockTypes'    => 'Block Types',
     212        'inserter'      => 'Inserter',
     213    );
     214
     215    /*
     216     * Register patterns for the active theme. If the theme is a child theme,
     217     * let it override any patterns from the parent theme that shares the same slug.
     218     */
     219    $themes     = array();
     220    $stylesheet = get_stylesheet();
     221    $template   = get_template();
     222    if ( $stylesheet !== $template ) {
     223        $themes[] = wp_get_theme( $stylesheet );
     224    }
     225    $themes[] = wp_get_theme( $template );
     226
     227    foreach ( $themes as $theme ) {
     228        $dirpath = $theme->get_stylesheet_directory() . '/patterns/';
     229        if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) {
     230            continue;
     231        }
     232        if ( file_exists( $dirpath ) ) {
     233            $files = glob( $dirpath . '*.php' );
     234            if ( $files ) {
     235                foreach ( $files as $file ) {
     236                    $pattern_data = get_file_data( $file, $default_headers );
     237
     238                    if ( empty( $pattern_data['slug'] ) ) {
     239                        _doing_it_wrong(
     240                            '_register_theme_block_patterns',
     241                            sprintf(
     242                                /* translators: %s: file name. */
     243                                __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ),
     244                                $file
     245                            ),
     246                            '6.0.0'
     247                        );
     248                        continue;
     249                    }
     250
     251                    if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) {
     252                        _doing_it_wrong(
     253                            '_register_theme_block_patterns',
     254                            sprintf(
     255                                /* translators: %1s: file name; %2s: slug value found. */
     256                                __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ),
     257                                $file,
     258                                $pattern_data['slug']
     259                            ),
     260                            '6.0.0'
     261                        );
     262                    }
     263
     264                    if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) {
     265                        continue;
     266                    }
     267
     268                    // Title is a required property.
     269                    if ( ! $pattern_data['title'] ) {
     270                        _doing_it_wrong(
     271                            '_register_theme_block_patterns',
     272                            sprintf(
     273                                /* translators: %1s: file name; %2s: slug value found. */
     274                                __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ),
     275                                $file
     276                            ),
     277                            '6.0.0'
     278                        );
     279                        continue;
     280                    }
     281
     282                    // For properties of type array, parse data as comma-separated.
     283                    foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) {
     284                        if ( ! empty( $pattern_data[ $property ] ) ) {
     285                            $pattern_data[ $property ] = array_filter(
     286                                preg_split(
     287                                    '/[\s,]+/',
     288                                    (string) $pattern_data[ $property ]
     289                                )
     290                            );
     291                        } else {
     292                            unset( $pattern_data[ $property ] );
     293                        }
     294                    }
     295
     296                    // Parse properties of type int.
     297                    foreach ( array( 'viewportWidth' ) as $property ) {
     298                        if ( ! empty( $pattern_data[ $property ] ) ) {
     299                            $pattern_data[ $property ] = (int) $pattern_data[ $property ];
     300                        } else {
     301                            unset( $pattern_data[ $property ] );
     302                        }
     303                    }
     304
     305                    // Parse properties of type bool.
     306                    foreach ( array( 'inserter' ) as $property ) {
     307                        if ( ! empty( $pattern_data[ $property ] ) ) {
     308                            $pattern_data[ $property ] = in_array(
     309                                strtolower( $pattern_data[ $property ] ),
     310                                array( 'yes', 'true' ),
     311                                true
     312                            );
     313                        } else {
     314                            unset( $pattern_data[ $property ] );
     315                        }
     316                    }
     317
     318                    // Translate the pattern metadata.
     319                    $text_domain = $theme->get( 'TextDomain' );
     320                    //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction
     321                    $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain );
     322                    if ( ! empty( $pattern_data['description'] ) ) {
     323                        //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction
     324                        $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain );
     325                    }
     326
     327                    // The actual pattern content is the output of the file.
     328                    ob_start();
     329                    include $file;
     330                    $pattern_data['content'] = ob_get_clean();
     331                    if ( ! $pattern_data['content'] ) {
     332                        continue;
     333                    }
     334
     335                    register_block_pattern( $pattern_data['slug'], $pattern_data );
     336                }
     337            }
     338        }
     339    }
     340}
     341add_action( 'init', '_register_theme_block_patterns' );
  • trunk/src/wp-includes/block-patterns/query-grid-posts.php

    r51443 r53152  
    1010    'blockTypes' => array( 'core/query' ),
    1111    'categories' => array( 'query' ),
    12     'content'    => '<!-- wp:query {"query":{"perPage":6,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"flex","columns":3}} -->
     12    'content'    => '<!-- wp:query {"query":{"perPage":6,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"flex","columns":3}} -->
    1313                    <div class="wp-block-query">
    1414                    <!-- wp:post-template -->
  • trunk/src/wp-includes/block-patterns/query-large-title-posts.php

    r51101 r53152  
    1111    'categories' => array( 'query' ),
    1212    'content'    => '<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"100px","right":"100px","bottom":"100px","left":"100px"}},"color":{"text":"#ffffff","background":"#000000"}}} -->
    13                     <div class="wp-block-group alignfull has-text-color has-background" style="background-color:#000000;color:#ffffff;padding-top:100px;padding-right:100px;padding-bottom:100px;padding-left:100px"><!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
     13                    <div class="wp-block-group alignfull has-text-color has-background" style="background-color:#000000;color:#ffffff;padding-top:100px;padding-right:100px;padding-bottom:100px;padding-left:100px"><!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
    1414                    <div class="wp-block-query"><!-- wp:post-template -->
    1515                    <!-- wp:separator {"customColor":"#ffffff","align":"wide","className":"is-style-wide"} -->
  • trunk/src/wp-includes/block-patterns/query-medium-posts.php

    r52069 r53152  
    1010    'blockTypes' => array( 'core/query' ),
    1111    'categories' => array( 'query' ),
    12     'content'    => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
     12    'content'    => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
    1313                    <div class="wp-block-query">
    1414                    <!-- wp:post-template -->
  • trunk/src/wp-includes/block-patterns/query-offset-posts.php

    r51241 r53152  
    1313                    <div class="wp-block-group" style="padding-top:30px;padding-right:30px;padding-bottom:30px;padding-left:30px"><!-- wp:columns -->
    1414                    <div class="wp-block-columns"><!-- wp:column {"width":"50%"} -->
    15                     <div class="wp-block-column" style="flex-basis:50%"><!-- wp:query {"query":{"perPage":2,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"list"}} -->
     15                    <div class="wp-block-column" style="flex-basis:50%"><!-- wp:query {"query":{"perPage":2,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"list"}} -->
    1616                    <div class="wp-block-query"><!-- wp:post-template -->
    1717                    <!-- wp:post-featured-image /-->
     
    2525                    <!-- /wp:column -->
    2626                    <!-- wp:column {"width":"50%"} -->
    27                     <div class="wp-block-column" style="flex-basis:50%"><!-- wp:query {"query":{"perPage":2,"pages":0,"offset":2,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"list"}} -->
     27                    <div class="wp-block-column" style="flex-basis:50%"><!-- wp:query {"query":{"perPage":2,"pages":0,"offset":2,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"exclude","inherit":false},"displayLayout":{"type":"list"}} -->
    2828                    <div class="wp-block-query"><!-- wp:post-template -->
    2929                    <!-- wp:spacer {"height":200} -->
  • trunk/src/wp-includes/block-patterns/query-small-posts.php

    r52069 r53152  
    1010    'blockTypes' => array( 'core/query' ),
    1111    'categories' => array( 'query' ),
    12     'content'    => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
     12    'content'    => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
    1313                    <div class="wp-block-query">
    1414                    <!-- wp:post-template -->
  • trunk/src/wp-includes/block-patterns/query-standard-posts.php

    r52069 r53152  
    1010    'blockTypes' => array( 'core/query' ),
    1111    'categories' => array( 'query' ),
    12     'content'    => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
     12    'content'    => '<!-- wp:query {"query":{"perPage":3,"pages":0,"offset":0,"postType":"post","order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":false}} -->
    1313                    <div class="wp-block-query">
    1414                    <!-- wp:post-template -->
  • trunk/src/wp-includes/blocks.php

    r53142 r53152  
    956956 * for subsequent `the_content` usage.
    957957 *
     958 * @since 5.0.0
    958959 * @access private
    959  *
    960  * @since 5.0.0
    961960 *
    962961 * @param string $content The post content running through this filter.
     
    11431142            $query['posts_per_page'] = $per_page;
    11441143        }
    1145         if ( ! empty( $block->context['query']['categoryIds'] ) ) {
    1146             $term_ids              = array_map( 'intval', $block->context['query']['categoryIds'] );
    1147             $term_ids              = array_filter( $term_ids );
    1148             $query['category__in'] = $term_ids;
    1149         }
    1150         if ( ! empty( $block->context['query']['tagIds'] ) ) {
    1151             $term_ids         = array_map( 'intval', $block->context['query']['tagIds'] );
    1152             $term_ids         = array_filter( $term_ids );
    1153             $query['tag__in'] = $term_ids;
     1144        // Migrate `categoryIds` and `tagIds` to `tax_query` for backwards compatibility.
     1145        if ( ! empty( $block->context['query']['categoryIds'] ) || ! empty( $block->context['query']['tagIds'] ) ) {
     1146            $tax_query = array();
     1147            if ( ! empty( $block->context['query']['categoryIds'] ) ) {
     1148                $tax_query[] = array(
     1149                    'taxonomy'         => 'category',
     1150                    'terms'            => array_filter( array_map( 'intval', $block->context['query']['categoryIds'] ) ),
     1151                    'include_children' => false,
     1152                );
     1153            }
     1154            if ( ! empty( $block->context['query']['tagIds'] ) ) {
     1155                $tax_query[] = array(
     1156                    'taxonomy'         => 'post_tag',
     1157                    'terms'            => array_filter( array_map( 'intval', $block->context['query']['tagIds'] ) ),
     1158                    'include_children' => false,
     1159                );
     1160            }
     1161            $query['tax_query'] = $tax_query;
     1162        }
     1163        if ( ! empty( $block->context['query']['taxQuery'] ) ) {
     1164            $query['tax_query'] = array();
     1165            foreach ( $block->context['query']['taxQuery'] as $taxonomy => $terms ) {
     1166                if ( is_taxonomy_viewable( $taxonomy ) && ! empty( $terms ) ) {
     1167                    $query['tax_query'][] = array(
     1168                        'taxonomy'         => $taxonomy,
     1169                        'terms'            => array_filter( array_map( 'intval', $terms ) ),
     1170                        'include_children' => false,
     1171                    );
     1172                }
     1173            }
    11541174        }
    11551175        if (
  • trunk/src/wp-includes/rest-api.php

    r52328 r53152  
    336336    // Pattern Directory.
    337337    $controller = new WP_REST_Pattern_Directory_Controller();
     338    $controller->register_routes();
     339
     340    // Block Patterns.
     341    $controller = new WP_REST_Block_Patterns_Controller();
     342    $controller->register_routes();
     343
     344    // Block Pattern Categories.
     345    $controller = new WP_REST_Block_Pattern_Categories_Controller();
    338346    $controller->register_routes();
    339347
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php

    r52236 r53152  
    8181     *
    8282     * @since 5.8.0
     83     * @since 6.0.0 Added 'slug' to request.
    8384     *
    8485     * @param WP_REST_Request $request Full details about the request.
     
    101102        $keyword_id  = $request['keyword'];
    102103        $search_term = $request['search'];
     104        $slug        = $request['slug'];
    103105
    104106        if ( $category_id ) {
     
    112114        if ( $search_term ) {
    113115            $query_args['search'] = $search_term;
     116        }
     117
     118        if ( $slug ) {
     119            $query_args['slug'] = $slug;
    114120        }
    115121
     
    160166                    'pattern_api_failed',
    161167                    sprintf(
    162                     /* translators: %s: Support forums URL. */
     168                        /* translators: %s: Support forums URL. */
    163169                        __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
    164170                        __( 'https://wordpress.org/support/forums/' )
     
    256262                    'type'        => 'integer',
    257263                    'minimum'     => 1,
    258                     'context'     => array( 'view', 'embed' ),
     264                    'context'     => array( 'view', 'edit', 'embed' ),
    259265                ),
    260266
     
    263269                    'type'        => 'string',
    264270                    'minLength'   => 1,
    265                     'context'     => array( 'view', 'embed' ),
     271                    'context'     => array( 'view', 'edit', 'embed' ),
    266272                ),
    267273
     
    270276                    'type'        => 'string',
    271277                    'minLength'   => 1,
    272                     'context'     => array( 'view', 'embed' ),
     278                    'context'     => array( 'view', 'edit', 'embed' ),
    273279                ),
    274280
     
    278284                    'uniqueItems' => true,
    279285                    'items'       => array( 'type' => 'string' ),
    280                     'context'     => array( 'view', 'embed' ),
     286                    'context'     => array( 'view', 'edit', 'embed' ),
    281287                ),
    282288
     
    286292                    'uniqueItems' => true,
    287293                    'items'       => array( 'type' => 'string' ),
    288                     'context'     => array( 'view', 'embed' ),
     294                    'context'     => array( 'view', 'edit', 'embed' ),
    289295                ),
    290296
     
    293299                    'type'        => 'string',
    294300                    'minLength'   => 1,
    295                     'context'     => array( 'view', 'embed' ),
     301                    'context'     => array( 'view', 'edit', 'embed' ),
    296302                ),
    297303
     
    299305                    'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ),
    300306                    'type'        => 'integer',
    301                     'context'     => array( 'view', 'embed' ),
     307                    'context'     => array( 'view', 'edit', 'embed' ),
    302308                ),
    303309            ),
  • trunk/src/wp-settings.php

    r53076 r53152  
    278278require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-edit-site-export-controller.php';
    279279require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php';
     280require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-patterns-controller.php';
     281require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-pattern-categories-controller.php';
    280282require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-application-passwords-controller.php';
    281283require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-site-health-controller.php';
  • trunk/tests/phpunit/tests/blocks/wpBlock.php

    r52010 r53152  
    444444                'orderby'      => 'title',
    445445                'post__not_in' => array( 1, 2 ),
    446                 'category__in' => array( 56 ),
    447                 'tag__in'      => array( 3, 11, 10 ),
     446                'tax_query'    => array(
     447                    array(
     448                        'taxonomy'         => 'category',
     449                        'terms'            => array( 56 ),
     450                        'include_children' => false,
     451                    ),
     452                    array(
     453                        'taxonomy'         => 'post_tag',
     454                        'terms'            => array( 3, 11, 10 ),
     455                        'include_children' => false,
     456                    ),
     457                ),
    448458            )
    449459        );
  • trunk/tests/phpunit/tests/rest-api/rest-pattern-directory-controller.php

    r51657 r53152  
    8080
    8181        $this->assertSame( 'view', $patterns['endpoints'][0]['args']['context']['default'] );
    82         $this->assertSame( array( 'view', 'embed' ), $patterns['endpoints'][0]['args']['context']['enum'] );
     82        $this->assertSame( array( 'view', 'embed', 'edit' ), $patterns['endpoints'][0]['args']['context']['enum'] );
    8383    }
    8484
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r53072 r53152  
    162162            '/wp/v2/plugins/(?P<plugin>[^.\/]+(?:\/[^.\/]+)?)',
    163163            '/wp/v2/block-directory/search',
     164            '/wp/v2/block-patterns/categories',
     165            '/wp/v2/block-patterns/patterns',
    164166            '/wp/v2/sidebars',
    165167            '/wp/v2/sidebars/(?P<id>[\w-]+)',
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r53072 r53152  
    1036310363                            "enum": [
    1036410364                                "view",
    10365                                 "embed"
     10365                                "embed",
     10366                                "edit"
    1036610367                            ],
    1036710368                            "default": "view",
     
    1039310394                    {
    1039410395                        "href": "http://example.org/index.php?rest_route=/wp/v2/pattern-directory/patterns"
     10396                    }
     10397                ]
     10398            }
     10399        },
     10400        "/wp/v2/block-patterns/patterns": {
     10401            "namespace": "wp/v2",
     10402            "methods": [
     10403                "GET"
     10404            ],
     10405            "endpoints": [
     10406                {
     10407                    "methods": [
     10408                        "GET"
     10409                    ],
     10410                    "args": []
     10411                }
     10412            ],
     10413            "_links": {
     10414                "self": [
     10415                    {
     10416                        "href": "http://example.org/index.php?rest_route=/wp/v2/block-patterns/patterns"
     10417                    }
     10418                ]
     10419            }
     10420        },
     10421        "/wp/v2/block-patterns/categories": {
     10422            "namespace": "wp/v2",
     10423            "methods": [
     10424                "GET"
     10425            ],
     10426            "endpoints": [
     10427                {
     10428                    "methods": [
     10429                        "GET"
     10430                    ],
     10431                    "args": []
     10432                }
     10433            ],
     10434            "_links": {
     10435                "self": [
     10436                    {
     10437                        "href": "http://example.org/index.php?rest_route=/wp/v2/block-patterns/categories"
    1039510438                    }
    1039610439                ]
Note: See TracChangeset for help on using the changeset viewer.