Make WordPress Core

Changeset 48171


Ignore:
Timestamp:
06/25/2020 10:11:09 PM (4 years ago)
Author:
TimothyBlynJacobs
Message:

Themes: Introduce register_theme_feature API.

Currently themes can declare support for a given feature by using add_theme_support(). This commit adds a register_theme_feature() API that allows plugins and WordPress Core to declare a list of available features that themes can support.

The REST API uses this to expose a theme's supported features if the feature has been registered with "show_in_rest" set to true.

Props kadamwhite, spacedmonkey, williampatton, desrosj, TimothyBlynJacobs.
Fixes #49406.

Location:
trunk
Files:
6 edited

Legend:

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

    r48121 r48171  
    471471add_action( 'delete_attachment', '_delete_attachment_theme_mod' );
    472472add_action( 'transition_post_status', '_wp_keep_alive_customize_changeset_dependent_auto_drafts', 20, 3 );
     473add_action( 'setup_theme', 'create_initial_theme_features', 0 );
    473474
    474475// Calendar widget cache.
  • trunk/src/wp-includes/rest-api.php

    r48150 r48171  
    17801780    return $data;
    17811781}
     1782
     1783/**
     1784 * Sets the "additionalProperties" to false by default for all object definitions in the schema.
     1785 *
     1786 * @since 5.5.0
     1787 *
     1788 * @param array $schema The schema to modify.
     1789 * @return array The modified schema.
     1790 */
     1791function rest_default_additional_properties_to_false( $schema ) {
     1792    $type = (array) $schema['type'];
     1793
     1794    if ( in_array( 'object', $type, true ) ) {
     1795        if ( isset( $schema['properties'] ) ) {
     1796            foreach ( $schema['properties'] as $key => $child_schema ) {
     1797                $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
     1798            }
     1799        }
     1800
     1801        if ( ! isset( $schema['additionalProperties'] ) ) {
     1802            $schema['additionalProperties'] = false;
     1803        }
     1804    }
     1805
     1806    if ( in_array( 'array', $type, true ) ) {
     1807        if ( isset( $schema['items'] ) ) {
     1808            $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
     1809        }
     1810    }
     1811
     1812    return $schema;
     1813}
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php

    r47929 r48171  
    168168
    169169        if ( rest_is_field_included( 'theme_supports', $fields ) ) {
    170             $item_schemas   = $this->get_item_schema();
    171             $theme_supports = $item_schemas['properties']['theme_supports']['properties'];
    172             foreach ( $theme_supports as $name => $schema ) {
     170            foreach ( get_registered_theme_features() as $feature => $config ) {
     171                if ( ! is_array( $config['show_in_rest'] ) ) {
     172                    continue;
     173                }
     174
     175                $name = $config['show_in_rest']['name'];
     176
    173177                if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) {
    174178                    continue;
    175179                }
    176180
    177                 if ( 'formats' === $name ) {
     181                if ( ! current_theme_supports( $feature ) ) {
     182                    $data['theme_supports'][ $name ] = $config['show_in_rest']['schema']['default'];
    178183                    continue;
    179184                }
    180185
    181                 if ( ! current_theme_supports( $name ) ) {
    182                     $data['theme_supports'][ $name ] = false;
     186                $support = get_theme_support( $feature );
     187
     188                if ( isset( $config['show_in_rest']['prepare_callback'] ) ) {
     189                    $prepare = $config['show_in_rest']['prepare_callback'];
     190                } else {
     191                    $prepare = array( $this, 'prepare_theme_support' );
     192                }
     193
     194                $prepared = $prepare( $support, $config, $feature, $request );
     195
     196                if ( is_wp_error( $prepared ) ) {
    183197                    continue;
    184198                }
    185199
    186                 if ( 'boolean' === $schema['type'] ) {
    187                     $data['theme_supports'][ $name ] = true;
    188                     continue;
    189                 }
    190 
    191                 $support = get_theme_support( $name );
    192 
    193                 if ( is_array( $support ) ) {
    194                     // None of the Core theme supports have variadic args.
    195                     $support = $support[0];
    196 
    197                     // Core multi-type theme-support schema definitions always list boolean first.
    198                     if ( is_array( $schema['type'] ) && 'boolean' === $schema['type'][0] ) {
    199                         // Pass the non-boolean type through to the sanitizer, which cannot itself
    200                         // determine the intended type if the value is invalid (for example if an
    201                         // object includes non-safelisted properties).
    202                         $schema['type'] = $schema['type'][1];
    203                     }
    204                 }
    205 
    206                 $data['theme_supports'][ $name ] = rest_sanitize_value_from_schema( $support, $schema );
    207             }
    208 
    209             $formats = get_theme_support( 'post-formats' );
    210             $formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
    211             $formats = array_merge( array( 'standard' ), $formats );
    212 
    213             $data['theme_supports']['formats'] = $formats;
     200                $data['theme_supports'][ $name ] = $prepared;
     201            }
    214202        }
    215203
     
    232220
    233221    /**
     222     * Prepares the theme support value for inclusion in the REST API response.
     223     *
     224     * @since 5.5.0
     225     *
     226     * @param mixed           $support The raw value from {@see get_theme_support()}
     227     * @param array           $args    The feature's registration args.
     228     * @param string          $feature The feature name.
     229     * @param WP_REST_Request $request The request object.
     230     * @return mixed The prepared support value.
     231     */
     232    protected function prepare_theme_support( $support, $args, $feature, $request ) {
     233        $schema = $args['show_in_rest']['schema'];
     234
     235        if ( 'boolean' === $schema['type'] ) {
     236            return true;
     237        }
     238
     239        if ( is_array( $support ) ) {
     240            if ( ! $args['variadic'] ) {
     241                $support = $support[0];
     242            }
     243
     244            // Multi-type theme-support schema definitions always list boolean first.
     245            if ( is_array( $schema['type'] ) && 'boolean' === $schema['type'][0] ) {
     246                // Pass the non-boolean type through to the sanitizer, which cannot itself
     247                // determine the intended type if the value is invalid (for example if an
     248                // object includes non-safelisted properties). See #50300.
     249                $schema['type'] = $schema['type'][1];
     250            }
     251        }
     252
     253        return rest_sanitize_value_from_schema( $support, $schema );
     254    }
     255
     256    /**
    234257     * Retrieves the theme's schema, conforming to JSON Schema.
    235258     *
     
    363386                    'type'        => 'object',
    364387                    'readonly'    => true,
    365                     'properties'  => array(
    366                         'align-wide'                => array(
    367                             'description' => __( 'Whether theme opts in to wide alignment CSS class.' ),
    368                             'type'        => 'boolean',
    369                         ),
    370                         'automatic-feed-links'      => array(
    371                             'description' => __( 'Whether posts and comments RSS feed links are added to head.' ),
    372                             'type'        => 'boolean',
    373                         ),
    374                         'custom-header'             => array(
    375                             'description'          => __( 'Custom header if defined by the theme.' ),
    376                             'type'                 => array( 'boolean', 'object' ),
    377                             'properties'           => array(
    378                                 'default-image'      => array(
    379                                     'type'   => 'string',
    380                                     'format' => 'uri',
    381                                 ),
    382                                 'random-default'     => array(
    383                                     'type' => 'boolean',
    384                                 ),
    385                                 'width'              => array(
    386                                     'type' => 'integer',
    387                                 ),
    388                                 'height'             => array(
    389                                     'type' => 'integer',
    390                                 ),
    391                                 'flex-height'        => array(
    392                                     'type' => 'boolean',
    393                                 ),
    394                                 'flex-width'         => array(
    395                                     'type' => 'boolean',
    396                                 ),
    397                                 'default-text-color' => array(
    398                                     'type' => 'string',
    399                                 ),
    400                                 'header-text'        => array(
    401                                     'type' => 'boolean',
    402                                 ),
    403                                 'uploads'            => array(
    404                                     'type' => 'boolean',
    405                                 ),
    406                                 'video'              => array(
    407                                     'type' => 'boolean',
    408                                 ),
    409                             ),
    410                             'additionalProperties' => false,
    411                         ),
    412                         'custom-background'         => array(
    413                             'description'          => __( 'Custom background if defined by the theme.' ),
    414                             'type'                 => array( 'boolean', 'object' ),
    415                             'properties'           => array(
    416                                 'default-image'      => array(
    417                                     'type'   => 'string',
    418                                     'format' => 'uri',
    419                                 ),
    420                                 'default-preset'     => array(
    421                                     'type' => 'string',
    422                                     'enum' => array(
    423                                         'default',
    424                                         'fill',
    425                                         'fit',
    426                                         'repeat',
    427                                         'custom',
    428                                     ),
    429                                 ),
    430                                 'default-position-x' => array(
    431                                     'type' => 'string',
    432                                     'enum' => array(
    433                                         'left',
    434                                         'center',
    435                                         'right',
    436                                     ),
    437                                 ),
    438                                 'default-position-y' => array(
    439                                     'type' => 'string',
    440                                     'enum' => array(
    441                                         'left',
    442                                         'center',
    443                                         'right',
    444                                     ),
    445                                 ),
    446                                 'default-size'       => array(
    447                                     'type' => 'string',
    448                                     'enum' => array(
    449                                         'auto',
    450                                         'contain',
    451                                         'cover',
    452                                     ),
    453                                 ),
    454                                 'default-repeat'     => array(
    455                                     'type' => 'string',
    456                                     'enum' => array(
    457                                         'repeat-x',
    458                                         'repeat-y',
    459                                         'repeat',
    460                                         'no-repeat',
    461                                     ),
    462                                 ),
    463                                 'default-attachment' => array(
    464                                     'type' => 'string',
    465                                     'enum' => array(
    466                                         'scroll',
    467                                         'fixed',
    468                                     ),
    469                                 ),
    470                                 'default-color'      => array(
    471                                     'type' => 'string',
    472                                 ),
    473                             ),
    474                             'additionalProperties' => false,
    475                         ),
    476                         'custom-logo'               => array(
    477                             'description'          => __( 'Custom logo if defined by the theme.' ),
    478                             'type'                 => array( 'boolean', 'object' ),
    479                             'properties'           => array(
    480                                 'width'       => array(
    481                                     'type' => 'integer',
    482                                 ),
    483                                 'height'      => array(
    484                                     'type' => 'integer',
    485                                 ),
    486                                 'flex-width'  => array(
    487                                     'type' => 'boolean',
    488                                 ),
    489                                 'flex-height' => array(
    490                                     'type' => 'boolean',
    491                                 ),
    492                                 'header-text' => array(
    493                                     'type'  => 'array',
    494                                     'items' => array(
    495                                         'type' => 'string',
    496                                     ),
    497                                 ),
    498                             ),
    499                             'additionalProperties' => false,
    500                         ),
    501                         'customize-selective-refresh-widgets' => array(
    502                             'description' => __( 'Whether the theme enables Selective Refresh for Widgets being managed with the Customizer.' ),
    503                             'type'        => 'boolean',
    504                         ),
    505                         'dark-editor-style'         => array(
    506                             'description' => __( 'Whether theme opts in to the dark editor style UI.' ),
    507                             'type'        => 'boolean',
    508                         ),
    509                         'disable-custom-colors'     => array(
    510                             'description' => __( 'Whether the theme disables custom colors.' ),
    511                             'type'        => 'boolean',
    512                         ),
    513                         'disable-custom-font-sizes' => array(
    514                             'description' => __( 'Whether the theme disables custom font sizes.' ),
    515                             'type'        => 'boolean',
    516                         ),
    517                         'disable-custom-gradients'  => array(
    518                             'description' => __( 'Whether the theme disables custom gradients.' ),
    519                             'type'        => 'boolean',
    520                         ),
    521                         'editor-color-palette'      => array(
    522                             'description' => __( 'Custom color palette if defined by the theme.' ),
    523                             'type'        => array( 'boolean', 'array' ),
    524                             'items'       => array(
    525                                 'type'                 => 'object',
    526                                 'properties'           => array(
    527                                     'name'  => array(
    528                                         'type' => 'string',
    529                                     ),
    530                                     'slug'  => array(
    531                                         'type' => 'string',
    532                                     ),
    533                                     'color' => array(
    534                                         'type' => 'string',
    535                                     ),
    536                                 ),
    537                                 'additionalProperties' => false,
    538                             ),
    539                         ),
    540                         'editor-font-sizes'         => array(
    541                             'description' => __( 'Custom font sizes if defined by the theme.' ),
    542                             'type'        => array( 'boolean', 'array' ),
    543                             'items'       => array(
    544                                 'type'                 => 'object',
    545                                 'properties'           => array(
    546                                     'name' => array(
    547                                         'type' => 'string',
    548                                     ),
    549                                     'size' => array(
    550                                         'type' => 'number',
    551                                     ),
    552                                     'slug' => array(
    553                                         'type' => 'string',
    554                                     ),
    555                                 ),
    556                                 'additionalProperties' => false,
    557                             ),
    558                         ),
    559                         'editor-gradient-presets'   => array(
    560                             'description' => __( 'Custom gradient presets if defined by the theme.' ),
    561                             'type'        => array( 'boolean', 'array' ),
    562                             'items'       => array(
    563                                 'type'                 => 'object',
    564                                 'properties'           => array(
    565                                     'name'     => array(
    566                                         'type' => 'string',
    567                                     ),
    568                                     'gradient' => array(
    569                                         'type' => 'string',
    570                                     ),
    571                                     'slug'     => array(
    572                                         'type' => 'string',
    573                                     ),
    574                                 ),
    575                                 'additionalProperties' => false,
    576                             ),
    577                         ),
    578                         'editor-styles'             => array(
    579                             'description' => __( 'Whether theme opts in to the editor styles CSS wrapper.' ),
    580                             'type'        => 'boolean',
    581                         ),
    582                         'formats'                   => array(
    583                             'description' => __( 'Post formats supported.' ),
    584                             'type'        => 'array',
    585                             'items'       => array(
    586                                 'type' => 'string',
    587                                 'enum' => get_post_format_slugs(),
    588                             ),
    589                         ),
    590                         'html5'                     => array(
    591                             'description' => __( 'Allows use of html5 markup for search forms, comment forms, comment lists, gallery, and caption.' ),
    592                             'type'        => array( 'boolean', 'array' ),
    593                             'items'       => array(
    594                                 'type' => 'string',
    595                                 'enum' => array(
    596                                     'search-form',
    597                                     'comment-form',
    598                                     'comment-list',
    599                                     'gallery',
    600                                     'caption',
    601                                     'script',
    602                                     'style',
    603                                 ),
    604                             ),
    605                         ),
    606                         'post-thumbnails'           => array(
    607                             'description' => __( 'Whether the theme supports post thumbnails.' ),
    608                             'type'        => array( 'boolean', 'array' ),
    609                             'items'       => array(
    610                                 'type' => 'string',
    611                             ),
    612                         ),
    613                         'responsive-embeds'         => array(
    614                             'description' => __( 'Whether the theme supports responsive embedded content.' ),
    615                             'type'        => 'boolean',
    616                         ),
    617                         'title-tag'                 => array(
    618                             'description' => __( 'Whether the theme can manage the document title tag.' ),
    619                             'type'        => 'boolean',
    620                         ),
    621                         'wp-block-styles'           => array(
    622                             'description' => __( 'Whether theme opts in to default WordPress block styles for viewing.' ),
    623                             'type'        => 'boolean',
    624                         ),
    625                     ),
     388                    'properties'  => array(),
    626389                ),
    627390                'theme_uri'      => array(
     
    649412            ),
    650413        );
     414
     415        foreach ( get_registered_theme_features() as $feature => $config ) {
     416            if ( ! is_array( $config['show_in_rest'] ) ) {
     417                continue;
     418            }
     419
     420            $name = $config['show_in_rest']['name'];
     421
     422            $schema['properties']['theme_supports']['properties'][ $name ] = $config['show_in_rest']['schema'];
     423        }
    651424
    652425        $this->schema = $schema;
  • trunk/src/wp-includes/theme.php

    r48169 r48171  
    29952995
    29962996/**
     2997 * Registers a theme feature for use in {@see add_theme_support}.
     2998 *
     2999 * This does not indicate that the current theme supports the feature, it only describes the feature's supported options.
     3000 *
     3001 * @since 5.5.0
     3002 *
     3003 * @global $_wp_registered_theme_features
     3004 *
     3005 * @param string $feature The name uniquely identifying the feature.
     3006 * @param array $args {
     3007 *      Data used to describe the theme
     3008 *
     3009 *      @type string     $type         The type of data associated with this feature. Defaults to 'boolean'.
     3010 *                                     Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'.
     3011 *      @type boolean    $variadic     Does this feature utilize the variadic support of {@see add_theme_support()},
     3012 *                                     or are all arguments specified as the second parameter. Must be used with the "array" type.
     3013 *      @type string     $description  A short description of the feature. Included in the Themes REST API schema. Intended for developers.
     3014 *      @type bool|array $show_in_rest {
     3015 *          Whether this feature should be included in the Themes REST API endpoint. Defaults to not being included.
     3016 *          When registering an 'array' or 'object' type, this argument must be an array with the 'schema' key.
     3017 *
     3018 *          @type array    $schema           Specifies the JSON Schema definition describing the feature. If any objects in the schema
     3019 *                                           do not include the 'additionalProperties' keyword, it is set to false.
     3020 *          @type string   $name             An alternate name to be use as the property name in the REST API.
     3021 *          @type callable $prepare_callback A function used to format the theme support in the REST API. Receives the raw theme support value.
     3022 *      }
     3023 * }
     3024 * @return true|WP_Error True if the theme feature was successfully registered, a WP_Error object if not.
     3025 */
     3026function register_theme_feature( $feature, $args = array() ) {
     3027    global $_wp_registered_theme_features;
     3028
     3029    if ( ! is_array( $_wp_registered_theme_features ) ) {
     3030        $_wp_registered_theme_features = array();
     3031    }
     3032
     3033    $defaults = array(
     3034        'type'         => 'boolean',
     3035        'variadic'     => false,
     3036        'description'  => '',
     3037        'show_in_rest' => false,
     3038    );
     3039
     3040    $args = wp_parse_args( $args, $defaults );
     3041
     3042    if ( true === $args['show_in_rest'] ) {
     3043        $args['show_in_rest'] = array();
     3044    }
     3045
     3046    if ( is_array( $args['show_in_rest'] ) ) {
     3047        $args['show_in_rest'] = wp_parse_args(
     3048            $args['show_in_rest'],
     3049            array(
     3050                'schema'           => array(),
     3051                'name'             => $feature,
     3052                'prepare_callback' => null,
     3053            )
     3054        );
     3055    }
     3056
     3057    if ( ! in_array( $args['type'], array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
     3058        return new WP_Error( 'invalid_type', __( 'The feature "type" is not valid JSON Schema type.' ) );
     3059    }
     3060
     3061    if ( true === $args['variadic'] && 'array' !== $args['type'] ) {
     3062        return new WP_Error( 'variadic_must_be_array', __( 'When registering a "variadic" theme feature, the "type" must be an "array".' ) );
     3063    }
     3064
     3065    if ( false !== $args['show_in_rest'] && in_array( $args['type'], array( 'array', 'object' ), true ) ) {
     3066        if ( ! is_array( $args['show_in_rest'] ) || empty( $args['show_in_rest']['schema'] ) ) {
     3067            return new WP_Error( 'missing_schema', __( 'When registering an "array" or "object" feature to show in the REST API, the feature\'s schema must also be defined.' ) );
     3068        }
     3069
     3070        if ( 'array' === $args['type'] && ! isset( $args['show_in_rest']['schema']['items'] ) ) {
     3071            return new WP_Error( 'missing_schema_items', __( 'When registering an "array" feature, the feature\'s schema must include the "items" keyword.' ) );
     3072        }
     3073
     3074        if ( 'object' === $args['type'] && ! isset( $args['show_in_rest']['schema']['properties'] ) ) {
     3075            return new WP_Error( 'missing_schema_properties', __( 'When registering an "object" feature, the feature\'s schema must include the "properties" keyword.' ) );
     3076        }
     3077    }
     3078
     3079    if ( is_array( $args['show_in_rest'] ) ) {
     3080        if ( isset( $args['show_in_rest']['prepare_callback'] ) && ! is_callable( $args['show_in_rest']['prepare_callback'] ) ) {
     3081            return new WP_Error( 'invalid_rest_prepare_callback', __( 'The prepare_callback must be a callable function.' ) );
     3082        }
     3083
     3084        $args['show_in_rest']['schema'] = wp_parse_args(
     3085            $args['show_in_rest']['schema'],
     3086            array(
     3087                'description' => $args['description'],
     3088                'type'        => $args['type'],
     3089                'default'     => false,
     3090            )
     3091        );
     3092
     3093        if ( is_bool( $args['show_in_rest']['schema']['default'] ) && ! in_array( 'boolean', (array) $args['show_in_rest']['schema']['type'], true ) ) {
     3094            // Automatically include the "boolean" type when the default value is a boolean.
     3095            $args['show_in_rest']['schema']['type'] = (array) $args['show_in_rest']['schema']['type'];
     3096            array_unshift( $args['show_in_rest']['schema']['type'], 'boolean' );
     3097        }
     3098
     3099        $args['show_in_rest']['schema'] = rest_default_additional_properties_to_false( $args['show_in_rest']['schema'] );
     3100    }
     3101
     3102    $_wp_registered_theme_features[ $feature ] = $args;
     3103
     3104    return true;
     3105}
     3106
     3107/**
     3108 * Gets the list of registered theme features.
     3109 *
     3110 * @since 5.5.0
     3111 *
     3112 * @global $_wp_registered_theme_features
     3113 *
     3114 * @return array[] List of theme features, keyed by their name.
     3115 */
     3116function get_registered_theme_features() {
     3117    global $_wp_registered_theme_features;
     3118
     3119    if ( ! is_array( $_wp_registered_theme_features ) ) {
     3120        return array();
     3121    }
     3122
     3123    return $_wp_registered_theme_features;
     3124}
     3125
     3126/**
     3127 * Gets the registration config for a theme feature.
     3128 *
     3129 * @since 5.5.0
     3130 *
     3131 * @global $_wp_registered_theme_features
     3132 *
     3133 * @param string $feature The feature name.
     3134 * @return array|null The registration args, or null if the feature was not registered.
     3135 */
     3136function get_registered_theme_feature( $feature ) {
     3137    global $_wp_registered_theme_features;
     3138
     3139    if ( ! is_array( $_wp_registered_theme_features ) ) {
     3140        return null;
     3141    }
     3142
     3143    return isset( $_wp_registered_theme_features[ $feature ] ) ? $_wp_registered_theme_features[ $feature ] : null;
     3144}
     3145
     3146/**
    29973147 * Checks an attachment being deleted to see if it's a header or background image.
    29983148 *
     
    34633613    }
    34643614}
     3615
     3616/**
     3617 * Creates the initial theme features when the 'setup_theme' action is fired.
     3618 *
     3619 * See {@see 'setup_theme'}.
     3620 *
     3621 * @since 5.5.0
     3622 */
     3623function create_initial_theme_features() {
     3624    register_theme_feature(
     3625        'align-wide',
     3626        array(
     3627            'description'  => __( 'Whether theme opts in to wide alignment CSS class.' ),
     3628            'show_in_rest' => true,
     3629        )
     3630    );
     3631    register_theme_feature(
     3632        'automatic-feed-links',
     3633        array(
     3634            'description'  => __( 'Whether posts and comments RSS feed links are added to head.' ),
     3635            'show_in_rest' => true,
     3636        )
     3637    );
     3638    register_theme_feature(
     3639        'custom-background',
     3640        array(
     3641            'description'  => __( 'Custom background if defined by the theme.' ),
     3642            'type'         => 'object',
     3643            'show_in_rest' => array(
     3644                'schema' => array(
     3645                    'properties' => array(
     3646                        'default-image'      => array(
     3647                            'type'   => 'string',
     3648                            'format' => 'uri',
     3649                        ),
     3650                        'default-preset'     => array(
     3651                            'type' => 'string',
     3652                            'enum' => array(
     3653                                'default',
     3654                                'fill',
     3655                                'fit',
     3656                                'repeat',
     3657                                'custom',
     3658                            ),
     3659                        ),
     3660                        'default-position-x' => array(
     3661                            'type' => 'string',
     3662                            'enum' => array(
     3663                                'left',
     3664                                'center',
     3665                                'right',
     3666                            ),
     3667                        ),
     3668                        'default-position-y' => array(
     3669                            'type' => 'string',
     3670                            'enum' => array(
     3671                                'left',
     3672                                'center',
     3673                                'right',
     3674                            ),
     3675                        ),
     3676                        'default-size'       => array(
     3677                            'type' => 'string',
     3678                            'enum' => array(
     3679                                'auto',
     3680                                'contain',
     3681                                'cover',
     3682                            ),
     3683                        ),
     3684                        'default-repeat'     => array(
     3685                            'type' => 'string',
     3686                            'enum' => array(
     3687                                'repeat-x',
     3688                                'repeat-y',
     3689                                'repeat',
     3690                                'no-repeat',
     3691                            ),
     3692                        ),
     3693                        'default-attachment' => array(
     3694                            'type' => 'string',
     3695                            'enum' => array(
     3696                                'scroll',
     3697                                'fixed',
     3698                            ),
     3699                        ),
     3700                        'default-color'      => array(
     3701                            'type' => 'string',
     3702                        ),
     3703                    ),
     3704                ),
     3705            ),
     3706        )
     3707    );
     3708    register_theme_feature(
     3709        'custom-header',
     3710        array(
     3711            'description'  => __( 'Custom header if defined by the theme.' ),
     3712            'type'         => 'object',
     3713            'show_in_rest' => array(
     3714                'schema' => array(
     3715                    'properties' => array(
     3716                        'default-image'      => array(
     3717                            'type'   => 'string',
     3718                            'format' => 'uri',
     3719                        ),
     3720                        'random-default'     => array(
     3721                            'type' => 'boolean',
     3722                        ),
     3723                        'width'              => array(
     3724                            'type' => 'integer',
     3725                        ),
     3726                        'height'             => array(
     3727                            'type' => 'integer',
     3728                        ),
     3729                        'flex-height'        => array(
     3730                            'type' => 'boolean',
     3731                        ),
     3732                        'flex-width'         => array(
     3733                            'type' => 'boolean',
     3734                        ),
     3735                        'default-text-color' => array(
     3736                            'type' => 'string',
     3737                        ),
     3738                        'header-text'        => array(
     3739                            'type' => 'boolean',
     3740                        ),
     3741                        'uploads'            => array(
     3742                            'type' => 'boolean',
     3743                        ),
     3744                        'video'              => array(
     3745                            'type' => 'boolean',
     3746                        ),
     3747                    ),
     3748                ),
     3749            ),
     3750        )
     3751    );
     3752    register_theme_feature(
     3753        'custom-logo',
     3754        array(
     3755            'type'         => 'object',
     3756            'description'  => __( 'Custom logo if defined by the theme.' ),
     3757            'show_in_rest' => array(
     3758                'schema' => array(
     3759                    'properties' => array(
     3760                        'width'       => array(
     3761                            'type' => 'integer',
     3762                        ),
     3763                        'height'      => array(
     3764                            'type' => 'integer',
     3765                        ),
     3766                        'flex-width'  => array(
     3767                            'type' => 'boolean',
     3768                        ),
     3769                        'flex-height' => array(
     3770                            'type' => 'boolean',
     3771                        ),
     3772                        'header-text' => array(
     3773                            'type'  => 'array',
     3774                            'items' => array(
     3775                                'type' => 'string',
     3776                            ),
     3777                        ),
     3778                    ),
     3779                ),
     3780            ),
     3781        )
     3782    );
     3783    register_theme_feature(
     3784        'customize-selective-refresh-widgets',
     3785        array(
     3786            'description'  => __( 'Whether the theme enables Selective Refresh for Widgets being managed with the Customizer.' ),
     3787            'show_in_rest' => true,
     3788        )
     3789    );
     3790    register_theme_feature(
     3791        'dark-editor-style',
     3792        array(
     3793            'description'  => __( 'Whether theme opts in to the dark editor style UI.' ),
     3794            'show_in_rest' => true,
     3795        )
     3796    );
     3797    register_theme_feature(
     3798        'disable-custom-colors',
     3799        array(
     3800            'description'  => __( 'Whether the theme disables custom colors.' ),
     3801            'show_in_rest' => true,
     3802        )
     3803    );
     3804    register_theme_feature(
     3805        'disable-custom-font-sizes',
     3806        array(
     3807            'description'  => __( 'Whether the theme disables custom font sizes.' ),
     3808            'show_in_rest' => true,
     3809        )
     3810    );
     3811    register_theme_feature(
     3812        'disable-custom-gradients',
     3813        array(
     3814            'description'  => __( 'Whether the theme disables custom gradients.' ),
     3815            'show_in_rest' => true,
     3816        )
     3817    );
     3818    register_theme_feature(
     3819        'editor-color-palette',
     3820        array(
     3821            'type'         => 'array',
     3822            'description'  => __( 'Custom color palette if defined by the theme.' ),
     3823            'show_in_rest' => array(
     3824                'schema' => array(
     3825                    'items' => array(
     3826                        'type'       => 'object',
     3827                        'properties' => array(
     3828                            'name'  => array(
     3829                                'type' => 'string',
     3830                            ),
     3831                            'slug'  => array(
     3832                                'type' => 'string',
     3833                            ),
     3834                            'color' => array(
     3835                                'type' => 'string',
     3836                            ),
     3837                        ),
     3838                    ),
     3839                ),
     3840            ),
     3841        )
     3842    );
     3843    register_theme_feature(
     3844        'editor-font-sizes',
     3845        array(
     3846            'type'         => 'array',
     3847            'description'  => __( 'Custom font sizes if defined by the theme.' ),
     3848            'show_in_rest' => array(
     3849                'schema' => array(
     3850                    'items' => array(
     3851                        'type'       => 'object',
     3852                        'properties' => array(
     3853                            'name' => array(
     3854                                'type' => 'string',
     3855                            ),
     3856                            'size' => array(
     3857                                'type' => 'number',
     3858                            ),
     3859                            'slug' => array(
     3860                                'type' => 'string',
     3861                            ),
     3862                        ),
     3863                    ),
     3864                ),
     3865            ),
     3866        )
     3867    );
     3868    register_theme_feature(
     3869        'editor-gradient-presets',
     3870        array(
     3871            'type'         => 'array',
     3872            'description'  => __( 'Custom gradient presets if defined by the theme.' ),
     3873            'show_in_rest' => array(
     3874                'schema' => array(
     3875                    'items' => array(
     3876                        'type'       => 'object',
     3877                        'properties' => array(
     3878                            'name'     => array(
     3879                                'type' => 'string',
     3880                            ),
     3881                            'gradient' => array(
     3882                                'type' => 'string',
     3883                            ),
     3884                            'slug'     => array(
     3885                                'type' => 'string',
     3886                            ),
     3887                        ),
     3888                    ),
     3889                ),
     3890            ),
     3891        )
     3892    );
     3893    register_theme_feature(
     3894        'editor-styles',
     3895        array(
     3896            'description'  => __( 'Whether theme opts in to the editor styles CSS wrapper.' ),
     3897            'show_in_rest' => true,
     3898        )
     3899    );
     3900    register_theme_feature(
     3901        'html5',
     3902        array(
     3903            'type'         => 'array',
     3904            'description'  => __( 'Allows use of HTML5 markup for search forms, comment forms, comment lists, gallery, and caption.' ),
     3905            'show_in_rest' => array(
     3906                'schema' => array(
     3907                    'items' => array(
     3908                        'type' => 'string',
     3909                        'enum' => array(
     3910                            'search-form',
     3911                            'comment-form',
     3912                            'comment-list',
     3913                            'gallery',
     3914                            'caption',
     3915                            'script',
     3916                            'style',
     3917                        ),
     3918                    ),
     3919                ),
     3920            ),
     3921        )
     3922    );
     3923    register_theme_feature(
     3924        'post-formats',
     3925        array(
     3926            'type'         => 'array',
     3927            'description'  => __( 'Post formats supported.' ),
     3928            'show_in_rest' => array(
     3929                'name'             => 'formats',
     3930                'schema'           => array(
     3931                    'items'   => array(
     3932                        'type' => 'string',
     3933                        'enum' => get_post_format_slugs(),
     3934                    ),
     3935                    'default' => array( 'standard' ),
     3936                ),
     3937                'prepare_callback' => static function ( $formats ) {
     3938                    $formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
     3939                    $formats = array_merge( array( 'standard' ), $formats );
     3940
     3941                    return $formats;
     3942                },
     3943            ),
     3944        )
     3945    );
     3946    register_theme_feature(
     3947        'post-thumbnails',
     3948        array(
     3949            'type'         => 'array',
     3950            'description'  => __( 'The post types that support thumbnails or true if all post types are supported.' ),
     3951            'show_in_rest' => array(
     3952                'type'   => array( 'boolean', 'array' ),
     3953                'schema' => array(
     3954                    'items' => array(
     3955                        'type' => 'string',
     3956                    ),
     3957                ),
     3958            ),
     3959        )
     3960    );
     3961    register_theme_feature(
     3962        'responsive-embeds',
     3963        array(
     3964            'description'  => __( 'Whether the theme supports responsive embedded content.' ),
     3965            'show_in_rest' => true,
     3966        )
     3967    );
     3968    register_theme_feature(
     3969        'title-tag',
     3970        array(
     3971            'description'  => __( 'Whether the theme can manage the document title tag.' ),
     3972            'show_in_rest' => true,
     3973        )
     3974    );
     3975    register_theme_feature(
     3976        'wp-block-styles',
     3977        array(
     3978            'description'  => __( 'Whether theme opts in to default WordPress block styles for viewing.' ),
     3979            'show_in_rest' => true,
     3980        )
     3981    );
     3982}
  • trunk/tests/phpunit/tests/rest-api/rest-themes-controller.php

    r47921 r48171  
    261261
    262262        $theme_supports = $properties['theme_supports']['properties'];
    263         $this->assertEquals( 20, count( $theme_supports ) );
    264263        $this->assertArrayHasKey( 'align-wide', $theme_supports );
    265264        $this->assertArrayHasKey( 'automatic-feed-links', $theme_supports );
     
    282281        $this->assertArrayHasKey( 'title-tag', $theme_supports );
    283282        $this->assertArrayHasKey( 'wp-block-styles', $theme_supports );
     283        $this->assertCount( 20, $theme_supports );
    284284    }
    285285
     
    999999
    10001000    /**
     1001     * @ticket 49406
     1002     */
     1003    public function test_variadic_theme_support() {
     1004        register_theme_feature(
     1005            'test-feature',
     1006            array(
     1007                'type'         => 'array',
     1008                'variadic'     => true,
     1009                'show_in_rest' => array(
     1010                    'schema' => array(
     1011                        'items' => array(
     1012                            'type' => 'string',
     1013                        ),
     1014                    ),
     1015                ),
     1016            )
     1017        );
     1018        add_theme_support( 'test-feature', 'a', 'b', 'c' );
     1019
     1020        $response = self::perform_active_theme_request();
     1021        $result   = $response->get_data();
     1022        $this->assertTrue( isset( $result[0]['theme_supports'] ) );
     1023        $this->assertEquals( array( 'a', 'b', 'c' ), $result[0]['theme_supports']['test-feature'] );
     1024    }
     1025
     1026    /**
    10011027     * It should be possible to register custom fields to the endpoint.
    10021028     *
  • trunk/tests/phpunit/tests/theme.php

    r47122 r48171  
    416416        $this->assertEquals( 'private', get_post_status( $nav_created_post_ids[1] ) );
    417417    }
     418
     419    /**
     420     * @ticket 49406
     421     */
     422    public function test_register_theme_support_defaults() {
     423        $registered = register_theme_feature( 'test-feature' );
     424        $this->assertTrue( $registered );
     425
     426        $expected = array(
     427            'type'         => 'boolean',
     428            'variadic'     => false,
     429            'description'  => '',
     430            'show_in_rest' => false,
     431        );
     432        $this->assertEqualSets( $expected, get_registered_theme_feature( 'test-feature' ) );
     433    }
     434
     435    /**
     436     * @ticket 49406
     437     */
     438    public function test_register_theme_support_explicit() {
     439        $args = array(
     440            'type'         => 'array',
     441            'variadic'     => true,
     442            'description'  => 'My Feature',
     443            'show_in_rest' => array(
     444                'schema' => array(
     445                    'items' => array(
     446                        'type' => 'string',
     447                    ),
     448                ),
     449            ),
     450        );
     451
     452        register_theme_feature( 'test-feature', $args );
     453        $actual = get_registered_theme_feature( 'test-feature' );
     454
     455        $this->assertEquals( 'array', $actual['type'] );
     456        $this->assertTrue( $actual['variadic'] );
     457        $this->assertEquals( 'My Feature', $actual['description'] );
     458        $this->assertEquals( array( 'type' => 'string' ), $actual['show_in_rest']['schema']['items'] );
     459    }
     460
     461    /**
     462     * @ticket 49406
     463     */
     464    public function test_register_theme_support_upgrades_show_in_rest() {
     465        register_theme_feature( 'test-feature', array( 'show_in_rest' => true ) );
     466
     467        $expected = array(
     468            'schema'           => array(
     469                'type'        => 'boolean',
     470                'description' => '',
     471                'default'     => false,
     472            ),
     473            'name'             => 'test-feature',
     474            'prepare_callback' => null,
     475        );
     476        $actual   = get_registered_theme_feature( 'test-feature' )['show_in_rest'];
     477
     478        $this->assertEqualSets( $expected, $actual );
     479    }
     480
     481    /**
     482     * @ticket 49406
     483     */
     484    public function test_register_theme_support_fills_schema() {
     485        register_theme_feature(
     486            'test-feature',
     487            array(
     488                'type'         => 'array',
     489                'description'  => 'Cool Feature',
     490                'show_in_rest' => array(
     491                    'schema' => array(
     492                        'items'    => array(
     493                            'type' => 'string',
     494                        ),
     495                        'minItems' => 1,
     496                    ),
     497                ),
     498            )
     499        );
     500
     501        $expected = array(
     502            'description' => 'Cool Feature',
     503            'type'        => array( 'boolean', 'array' ),
     504            'items'       => array(
     505                'type' => 'string',
     506            ),
     507            'minItems'    => 1,
     508            'default'     => false,
     509        );
     510        $actual   = get_registered_theme_feature( 'test-feature' )['show_in_rest']['schema'];
     511
     512        $this->assertEqualSets( $expected, $actual );
     513    }
     514
     515    /**
     516     * @ticket 49406
     517     */
     518    public function test_register_theme_support_does_not_add_boolean_type_if_non_bool_default() {
     519        register_theme_feature(
     520            'test-feature',
     521            array(
     522                'type'         => 'array',
     523                'show_in_rest' => array(
     524                    'schema' => array(
     525                        'items'   => array(
     526                            'type' => 'string',
     527                        ),
     528                        'default' => array( 'standard' ),
     529                    ),
     530                ),
     531            )
     532        );
     533
     534        $actual = get_registered_theme_feature( 'test-feature' )['show_in_rest']['schema']['type'];
     535        $this->assertEquals( 'array', $actual );
     536    }
     537
     538    /**
     539     * @ticket 49406
     540     */
     541    public function test_register_theme_support_defaults_additional_properties_to_false() {
     542        register_theme_feature(
     543            'test-feature',
     544            array(
     545                'type'         => 'object',
     546                'description'  => 'Cool Feature',
     547                'show_in_rest' => array(
     548                    'schema' => array(
     549                        'properties' => array(
     550                            'a' => array(
     551                                'type' => 'string',
     552                            ),
     553                        ),
     554                    ),
     555                ),
     556            )
     557        );
     558
     559        $actual = get_registered_theme_feature( 'test-feature' )['show_in_rest']['schema'];
     560
     561        $this->assertArrayHasKey( 'additionalProperties', $actual );
     562        $this->assertFalse( $actual['additionalProperties'] );
     563    }
     564
     565    /**
     566     * @ticket 49406
     567     */
     568    public function test_register_theme_support_with_additional_properties() {
     569        register_theme_feature(
     570            'test-feature',
     571            array(
     572                'type'         => 'object',
     573                'description'  => 'Cool Feature',
     574                'show_in_rest' => array(
     575                    'schema' => array(
     576                        'properties'           => array(),
     577                        'additionalProperties' => array(
     578                            'type' => 'string',
     579                        ),
     580                    ),
     581                ),
     582            )
     583        );
     584
     585        $expected = array(
     586            'type' => 'string',
     587        );
     588        $actual   = get_registered_theme_feature( 'test-feature' )['show_in_rest']['schema']['additionalProperties'];
     589
     590        $this->assertEqualSets( $expected, $actual );
     591    }
     592
     593    /**
     594     * @ticket 49406
     595     */
     596    public function test_register_theme_support_defaults_additional_properties_to_false_in_array() {
     597        register_theme_feature(
     598            'test-feature',
     599            array(
     600                'type'         => 'array',
     601                'description'  => 'Cool Feature',
     602                'show_in_rest' => array(
     603                    'schema' => array(
     604                        'items' => array(
     605                            'type'       => 'object',
     606                            'properties' => array(
     607                                'a' => array(
     608                                    'type' => 'string',
     609                                ),
     610                            ),
     611                        ),
     612                    ),
     613                ),
     614            )
     615        );
     616
     617        $actual = get_registered_theme_feature( 'test-feature' )['show_in_rest']['schema']['items'];
     618
     619        $this->assertArrayHasKey( 'additionalProperties', $actual );
     620        $this->assertFalse( $actual['additionalProperties'] );
     621    }
     622
     623    /**
     624     * @ticket       49406
     625     * @dataProvider _dp_register_theme_support_validation
     626     * @param string $error_code The error code expected.
     627     * @param array  $args       The args to register.
     628     */
     629    public function test_register_theme_support_validation( $error_code, $args ) {
     630        $registered = register_theme_feature( 'test-feature', $args );
     631
     632        $this->assertWPError( $registered );
     633        $this->assertEquals( $error_code, $registered->get_error_code() );
     634    }
     635
     636    public function _dp_register_theme_support_validation() {
     637        return array(
     638            array(
     639                'invalid_type',
     640                array(
     641                    'type' => 'float',
     642                ),
     643            ),
     644            array(
     645                'invalid_type',
     646                array(
     647                    'type' => array( 'string' ),
     648                ),
     649            ),
     650            array(
     651                'variadic_must_be_array',
     652                array(
     653                    'variadic' => true,
     654                ),
     655            ),
     656            array(
     657                'missing_schema',
     658                array(
     659                    'type'         => 'object',
     660                    'show_in_rest' => true,
     661                ),
     662            ),
     663            array(
     664                'missing_schema',
     665                array(
     666                    'type'         => 'array',
     667                    'show_in_rest' => true,
     668                ),
     669            ),
     670            array(
     671                'missing_schema_items',
     672                array(
     673                    'type'         => 'array',
     674                    'show_in_rest' => array(
     675                        'schema' => array(
     676                            'type' => 'array',
     677                        ),
     678                    ),
     679                ),
     680            ),
     681            array(
     682                'missing_schema_properties',
     683                array(
     684                    'type'         => 'object',
     685                    'show_in_rest' => array(
     686                        'schema' => array(
     687                            'type' => 'object',
     688                        ),
     689                    ),
     690                ),
     691            ),
     692            array(
     693                'invalid_rest_prepare_callback',
     694                array(
     695                    'show_in_rest' => array(
     696                        'prepare_callback' => 'this is not a valid function',
     697                    ),
     698                ),
     699            ),
     700        );
     701    }
    418702}
Note: See TracChangeset for help on using the changeset viewer.