Make WordPress Core


Ignore:
Timestamp:
10/20/2020 08:17:20 PM (4 years ago)
Author:
TimothyBlynJacobs
Message:

REST API: Make sure all supported JSON Schema keywords are output in the index.

Previously, only a small subset of keywords were exposed which limited the utility of OPTIONS requests.

Props raubvogel, TimothyBlynJacobs.
Fixes #51020.

File:
1 edited

Legend:

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

    r49246 r49257  
    18761876
    18771877/**
    1878  * Validate a value based on a schema.
    1879  *
    1880  * @since 4.7.0
    1881  * @since 4.9.0 Support the "object" type.
    1882  * @since 5.2.0 Support validating "additionalProperties" against a schema.
    1883  * @since 5.3.0 Support multiple types.
    1884  * @since 5.4.0 Convert an empty string to an empty object.
    1885  * @since 5.5.0 Add the "uuid" and "hex-color" formats.
    1886  *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
    1887  *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
    1888  *              Validate required properties.
    1889  * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
    1890  *              Support the "multipleOf" keyword for numbers and integers.
    1891  *              Support the "patternProperties" keyword for objects.
    1892  *              Support the "anyOf" and "oneOf" keywords.
    1893  *
    1894  * @param mixed  $value The value to validate.
    1895  * @param array  $args  Schema array to use for validation.
    1896  * @param string $param The parameter name, used in error messages.
    1897  * @return true|WP_Error
    1898  */
    1899 function rest_validate_value_from_schema( $value, $args, $param = '' ) {
    1900     if ( isset( $args['anyOf'] ) ) {
    1901         $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
    1902         if ( is_wp_error( $matching_schema ) ) {
    1903             return $matching_schema;
    1904         }
    1905 
    1906         if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
    1907             $args['type'] = $matching_schema['type'];
    1908         }
    1909     }
    1910 
    1911     if ( isset( $args['oneOf'] ) ) {
    1912         $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
    1913         if ( is_wp_error( $matching_schema ) ) {
    1914             return $matching_schema;
    1915         }
    1916 
    1917         if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
    1918             $args['type'] = $matching_schema['type'];
    1919         }
    1920     }
    1921 
    1922     $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
    1923 
    1924     if ( ! isset( $args['type'] ) ) {
    1925         /* translators: %s: Parameter. */
    1926         _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
    1927     }
    1928 
    1929     if ( is_array( $args['type'] ) ) {
    1930         $best_type = rest_handle_multi_type_schema( $value, $args, $param );
    1931 
    1932         if ( ! $best_type ) {
    1933             return new WP_Error(
    1934                 'rest_invalid_type',
    1935                 /* translators: 1: Parameter, 2: List of types. */
    1936                 sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
    1937                 array( 'param' => $param )
    1938             );
    1939         }
    1940 
    1941         $args['type'] = $best_type;
    1942     }
    1943 
    1944     if ( ! in_array( $args['type'], $allowed_types, true ) ) {
    1945         _doing_it_wrong(
    1946             __FUNCTION__,
    1947             /* translators: 1: Parameter, 2: The list of allowed types. */
    1948             wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
    1949             '5.5.0'
    1950         );
    1951     }
    1952 
    1953     if ( 'array' === $args['type'] ) {
    1954         if ( ! rest_is_array( $value ) ) {
    1955             return new WP_Error(
    1956                 'rest_invalid_type',
    1957                 /* translators: 1: Parameter, 2: Type name. */
    1958                 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
    1959                 array( 'param' => $param )
    1960             );
    1961         }
    1962 
    1963         $value = rest_sanitize_array( $value );
    1964 
    1965         if ( isset( $args['items'] ) ) {
    1966             foreach ( $value as $index => $v ) {
    1967                 $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
    1968                 if ( is_wp_error( $is_valid ) ) {
    1969                     return $is_valid;
    1970                 }
    1971             }
    1972         }
    1973 
    1974         if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
    1975             /* translators: 1: Parameter, 2: Number. */
    1976             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s items.' ), $param, number_format_i18n( $args['minItems'] ) ) );
    1977         }
    1978 
    1979         if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
    1980             /* translators: 1: Parameter, 2: Number. */
    1981             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s items.' ), $param, number_format_i18n( $args['maxItems'] ) ) );
    1982         }
    1983 
    1984         if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
    1985             /* translators: 1: Parameter. */
    1986             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
    1987         }
    1988     }
    1989 
    1990     if ( 'object' === $args['type'] ) {
    1991         if ( ! rest_is_object( $value ) ) {
    1992             return new WP_Error(
    1993                 'rest_invalid_type',
    1994                 /* translators: 1: Parameter, 2: Type name. */
    1995                 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
    1996                 array( 'param' => $param )
    1997             );
    1998         }
    1999 
    2000         $value = rest_sanitize_object( $value );
    2001 
    2002         if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
    2003             foreach ( $args['required'] as $name ) {
    2004                 if ( ! array_key_exists( $name, $value ) ) {
    2005                     /* translators: 1: Property of an object, 2: Parameter. */
    2006                     return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
    2007                 }
    2008             }
    2009         } elseif ( isset( $args['properties'] ) ) { // schema version 3
    2010             foreach ( $args['properties'] as $name => $property ) {
    2011                 if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
    2012                     /* translators: 1: Property of an object, 2: Parameter. */
    2013                     return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
    2014                 }
    2015             }
    2016         }
    2017 
    2018         foreach ( $value as $property => $v ) {
    2019             if ( isset( $args['properties'][ $property ] ) ) {
    2020                 $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
    2021                 if ( is_wp_error( $is_valid ) ) {
    2022                     return $is_valid;
    2023                 }
    2024                 continue;
    2025             }
    2026 
    2027             $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
    2028             if ( null !== $pattern_property_schema ) {
    2029                 $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
    2030                 if ( is_wp_error( $is_valid ) ) {
    2031                     return $is_valid;
    2032                 }
    2033                 continue;
    2034             }
    2035 
    2036             if ( isset( $args['additionalProperties'] ) ) {
    2037                 if ( false === $args['additionalProperties'] ) {
    2038                     /* translators: %s: Property of an object. */
    2039                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
    2040                 }
    2041 
    2042                 if ( is_array( $args['additionalProperties'] ) ) {
    2043                     $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
    2044                     if ( is_wp_error( $is_valid ) ) {
    2045                         return $is_valid;
    2046                     }
    2047                 }
    2048             }
    2049         }
    2050 
    2051         if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
    2052             /* translators: 1: Parameter, 2: Number. */
    2053             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s properties.' ), $param, number_format_i18n( $args['minProperties'] ) ) );
    2054         }
    2055 
    2056         if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
    2057             /* translators: 1: Parameter, 2: Number. */
    2058             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s properties.' ), $param, number_format_i18n( $args['maxProperties'] ) ) );
    2059         }
    2060     }
    2061 
    2062     if ( 'null' === $args['type'] ) {
    2063         if ( null !== $value ) {
    2064             return new WP_Error(
    2065                 'rest_invalid_type',
    2066                 /* translators: 1: Parameter, 2: Type name. */
    2067                 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
    2068                 array( 'param' => $param )
    2069             );
    2070         }
    2071 
    2072         return true;
    2073     }
    2074 
    2075     if ( ! empty( $args['enum'] ) ) {
    2076         if ( ! in_array( $value, $args['enum'], true ) ) {
    2077             /* translators: 1: Parameter, 2: List of valid values. */
    2078             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
    2079         }
    2080     }
    2081 
    2082     if ( in_array( $args['type'], array( 'integer', 'number' ), true ) ) {
    2083         if ( ! is_numeric( $value ) ) {
    2084             return new WP_Error(
    2085                 'rest_invalid_type',
    2086                 /* translators: 1: Parameter, 2: Type name. */
    2087                 sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
    2088                 array( 'param' => $param )
    2089             );
    2090         }
    2091 
    2092         if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
    2093             /* translators: 1: Parameter, 2: Multiplier. */
    2094             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] ) );
    2095         }
    2096     }
    2097 
    2098     if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
    2099         return new WP_Error(
    2100             'rest_invalid_type',
    2101             /* translators: 1: Parameter, 2: Type name. */
    2102             sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
    2103             array( 'param' => $param )
    2104         );
    2105     }
    2106 
    2107     if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
    2108         return new WP_Error(
    2109             'rest_invalid_type',
    2110             /* translators: 1: Parameter, 2: Type name. */
    2111             sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
    2112             array( 'param' => $param )
    2113         );
    2114     }
    2115 
    2116     if ( 'string' === $args['type'] ) {
    2117         if ( ! is_string( $value ) ) {
    2118             return new WP_Error(
    2119                 'rest_invalid_type',
    2120                 /* translators: 1: Parameter, 2: Type name. */
    2121                 sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
    2122                 array( 'param' => $param )
    2123             );
    2124         }
    2125 
    2126         if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
    2127             return new WP_Error(
    2128                 'rest_invalid_param',
    2129                 sprintf(
    2130                     /* translators: 1: Parameter, 2: Number of characters. */
    2131                     _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ),
    2132                     $param,
    2133                     number_format_i18n( $args['minLength'] )
    2134                 )
    2135             );
    2136         }
    2137 
    2138         if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
    2139             return new WP_Error(
    2140                 'rest_invalid_param',
    2141                 sprintf(
    2142                     /* translators: 1: Parameter, 2: Number of characters. */
    2143                     _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ),
    2144                     $param,
    2145                     number_format_i18n( $args['maxLength'] )
    2146                 )
    2147             );
    2148         }
    2149 
    2150         if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
    2151             /* translators: 1: Parameter, 2: Pattern. */
    2152             return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) );
    2153         }
    2154     }
    2155 
    2156     // The "format" keyword should only be applied to strings. However, for backward compatibility,
    2157     // we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
    2158     if ( isset( $args['format'] )
    2159         && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
    2160     ) {
    2161         switch ( $args['format'] ) {
    2162             case 'hex-color':
    2163                 if ( ! rest_parse_hex_color( $value ) ) {
    2164                     return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
    2165                 }
    2166                 break;
    2167 
    2168             case 'date-time':
    2169                 if ( ! rest_parse_date( $value ) ) {
    2170                     return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
    2171                 }
    2172                 break;
    2173 
    2174             case 'email':
    2175                 if ( ! is_email( $value ) ) {
    2176                     return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
    2177                 }
    2178                 break;
    2179             case 'ip':
    2180                 if ( ! rest_is_ip_address( $value ) ) {
    2181                     /* translators: %s: IP address. */
    2182                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) );
    2183                 }
    2184                 break;
    2185             case 'uuid':
    2186                 if ( ! wp_is_uuid( $value ) ) {
    2187                     /* translators: %s: The name of a JSON field expecting a valid UUID. */
    2188                     return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
    2189                 }
    2190                 break;
    2191         }
    2192     }
    2193 
    2194     if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
    2195         if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
    2196             if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
    2197                 /* translators: 1: Parameter, 2: Minimum number. */
    2198                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
    2199             } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
    2200                 /* translators: 1: Parameter, 2: Minimum number. */
    2201                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
    2202             }
    2203         } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
    2204             if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
    2205                 /* translators: 1: Parameter, 2: Maximum number. */
    2206                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
    2207             } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
    2208                 /* translators: 1: Parameter, 2: Maximum number. */
    2209                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
    2210             }
    2211         } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
    2212             if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
    2213                 if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
    2214                     /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
    2215                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
    2216                 }
    2217             } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
    2218                 if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
    2219                     /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
    2220                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
    2221                 }
    2222             } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
    2223                 if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
    2224                     /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
    2225                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
    2226                 }
    2227             } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
    2228                 if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
    2229                     /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
    2230                     return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
    2231                 }
    2232             }
    2233         }
    2234     }
    2235 
    2236     return true;
    2237 }
    2238 
    2239 /**
    2240  * Sanitize a value based on a schema.
    2241  *
    2242  * @since 4.7.0
    2243  * @since 5.5.0 Added the `$param` parameter.
    2244  * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
    2245  *
    2246  * @param mixed  $value The value to sanitize.
    2247  * @param array  $args  Schema array to use for sanitization.
    2248  * @param string $param The parameter name, used in error messages.
    2249  * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
    2250  */
    2251 function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
    2252     if ( isset( $args['anyOf'] ) ) {
    2253         $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
    2254         if ( is_wp_error( $matching_schema ) ) {
    2255             return $matching_schema;
    2256         }
    2257 
    2258         if ( ! isset( $args['type'] ) ) {
    2259             $args['type'] = $matching_schema['type'];
    2260         }
    2261 
    2262         $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
    2263     }
    2264 
    2265     if ( isset( $args['oneOf'] ) ) {
    2266         $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
    2267         if ( is_wp_error( $matching_schema ) ) {
    2268             return $matching_schema;
    2269         }
    2270 
    2271         if ( ! isset( $args['type'] ) ) {
    2272             $args['type'] = $matching_schema['type'];
    2273         }
    2274 
    2275         $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
    2276     }
    2277 
    2278     $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
    2279 
    2280     if ( ! isset( $args['type'] ) ) {
    2281         /* translators: %s: Parameter. */
    2282         _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
    2283     }
    2284 
    2285     if ( is_array( $args['type'] ) ) {
    2286         $best_type = rest_handle_multi_type_schema( $value, $args, $param );
    2287 
    2288         if ( ! $best_type ) {
    2289             return null;
    2290         }
    2291 
    2292         $args['type'] = $best_type;
    2293     }
    2294 
    2295     if ( ! in_array( $args['type'], $allowed_types, true ) ) {
    2296         _doing_it_wrong(
    2297             __FUNCTION__,
    2298             /* translators: 1: Parameter, 2: The list of allowed types. */
    2299             wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
    2300             '5.5.0'
    2301         );
    2302     }
    2303 
    2304     if ( 'array' === $args['type'] ) {
    2305         $value = rest_sanitize_array( $value );
    2306 
    2307         if ( ! empty( $args['items'] ) ) {
    2308             foreach ( $value as $index => $v ) {
    2309                 $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
    2310             }
    2311         }
    2312 
    2313         if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
    2314             /* translators: 1: Parameter. */
    2315             return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
    2316         }
    2317 
    2318         return $value;
    2319     }
    2320 
    2321     if ( 'object' === $args['type'] ) {
    2322         $value = rest_sanitize_object( $value );
    2323 
    2324         foreach ( $value as $property => $v ) {
    2325             if ( isset( $args['properties'][ $property ] ) ) {
    2326                 $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
    2327                 continue;
    2328             }
    2329 
    2330             $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
    2331             if ( null !== $pattern_property_schema ) {
    2332                 $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
    2333                 continue;
    2334             }
    2335 
    2336             if ( isset( $args['additionalProperties'] ) ) {
    2337                 if ( false === $args['additionalProperties'] ) {
    2338                     unset( $value[ $property ] );
    2339                 } elseif ( is_array( $args['additionalProperties'] ) ) {
    2340                     $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
    2341                 }
    2342             }
    2343         }
    2344 
    2345         return $value;
    2346     }
    2347 
    2348     if ( 'null' === $args['type'] ) {
    2349         return null;
    2350     }
    2351 
    2352     if ( 'integer' === $args['type'] ) {
    2353         return (int) $value;
    2354     }
    2355 
    2356     if ( 'number' === $args['type'] ) {
    2357         return (float) $value;
    2358     }
    2359 
    2360     if ( 'boolean' === $args['type'] ) {
    2361         return rest_sanitize_boolean( $value );
    2362     }
    2363 
    2364     // This behavior matches rest_validate_value_from_schema().
    2365     if ( isset( $args['format'] )
    2366         && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
    2367     ) {
    2368         switch ( $args['format'] ) {
    2369             case 'hex-color':
    2370                 return (string) sanitize_hex_color( $value );
    2371 
    2372             case 'date-time':
    2373                 return sanitize_text_field( $value );
    2374 
    2375             case 'email':
    2376                 // sanitize_email() validates, which would be unexpected.
    2377                 return sanitize_text_field( $value );
    2378 
    2379             case 'uri':
    2380                 return esc_url_raw( $value );
    2381 
    2382             case 'ip':
    2383                 return sanitize_text_field( $value );
    2384 
    2385             case 'uuid':
    2386                 return sanitize_text_field( $value );
    2387         }
    2388     }
    2389 
    2390     if ( 'string' === $args['type'] ) {
    2391         return (string) $value;
    2392     }
    2393 
    2394     return $value;
    2395 }
    2396 
    2397 /**
    2398  * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
    2399  * Expected to be called in the context of `array_reduce`.
    2400  *
    2401  * @since 5.0.0
    2402  *
    2403  * @param array  $memo Reduce accumulator.
    2404  * @param string $path REST API path to preload.
    2405  * @return array Modified reduce accumulator.
    2406  */
    2407 function rest_preload_api_request( $memo, $path ) {
    2408     // array_reduce() doesn't support passing an array in PHP 5.2,
    2409     // so we need to make sure we start with one.
    2410     if ( ! is_array( $memo ) ) {
    2411         $memo = array();
    2412     }
    2413 
    2414     if ( empty( $path ) ) {
    2415         return $memo;
    2416     }
    2417 
    2418     $method = 'GET';
    2419     if ( is_array( $path ) && 2 === count( $path ) ) {
    2420         $method = end( $path );
    2421         $path   = reset( $path );
    2422 
    2423         if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
    2424             $method = 'GET';
    2425         }
    2426     }
    2427 
    2428     $path_parts = parse_url( $path );
    2429     if ( false === $path_parts ) {
    2430         return $memo;
    2431     }
    2432 
    2433     $request = new WP_REST_Request( $method, $path_parts['path'] );
    2434     if ( ! empty( $path_parts['query'] ) ) {
    2435         parse_str( $path_parts['query'], $query_params );
    2436         $request->set_query_params( $query_params );
    2437     }
    2438 
    2439     $response = rest_do_request( $request );
    2440     if ( 200 === $response->status ) {
    2441         $server = rest_get_server();
    2442         $data   = (array) $response->get_data();
    2443         $links  = $server::get_compact_response_links( $response );
    2444         if ( ! empty( $links ) ) {
    2445             $data['_links'] = $links;
    2446         }
    2447 
    2448         if ( 'OPTIONS' === $method ) {
    2449             $response = rest_send_allow_header( $response, $server, $request );
    2450 
    2451             $memo[ $method ][ $path ] = array(
    2452                 'body'    => $data,
    2453                 'headers' => $response->headers,
    2454             );
    2455         } else {
    2456             $memo[ $path ] = array(
    2457                 'body'    => $data,
    2458                 'headers' => $response->headers,
    2459             );
    2460         }
    2461     }
    2462 
    2463     return $memo;
    2464 }
    2465 
    2466 /**
    2467  * Parses the "_embed" parameter into the list of resources to embed.
    2468  *
    2469  * @since 5.4.0
    2470  *
    2471  * @param string|array $embed Raw "_embed" parameter value.
    2472  * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
    2473  */
    2474 function rest_parse_embed_param( $embed ) {
    2475     if ( ! $embed || 'true' === $embed || '1' === $embed ) {
    2476         return true;
    2477     }
    2478 
    2479     $rels = wp_parse_list( $embed );
    2480 
    2481     if ( ! $rels ) {
    2482         return true;
    2483     }
    2484 
    2485     return $rels;
    2486 }
    2487 
    2488 /**
    2489  * Filters the response to remove any fields not available in the given context.
    2490  *
    2491  * @since 5.5.0
    2492  * @since 5.6.0 Support the "patternProperties" keyword for objects.
    2493  *              Support the "anyOf" and "oneOf" keywords.
    2494  *
    2495  * @param array|object $data    The response data to modify.
    2496  * @param array        $schema  The schema for the endpoint used to filter the response.
    2497  * @param string       $context The requested context.
    2498  * @return array|object The filtered response data.
    2499  */
    2500 function rest_filter_response_by_context( $data, $schema, $context ) {
    2501     if ( isset( $schema['anyOf'] ) ) {
    2502         $matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
    2503         if ( ! is_wp_error( $matching_schema ) ) {
    2504             if ( ! isset( $schema['type'] ) ) {
    2505                 $schema['type'] = $matching_schema['type'];
    2506             }
    2507 
    2508             $data = rest_filter_response_by_context( $data, $matching_schema, $context );
    2509         }
    2510     }
    2511 
    2512     if ( isset( $schema['oneOf'] ) ) {
    2513         $matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
    2514         if ( ! is_wp_error( $matching_schema ) ) {
    2515             if ( ! isset( $schema['type'] ) ) {
    2516                 $schema['type'] = $matching_schema['type'];
    2517             }
    2518 
    2519             $data = rest_filter_response_by_context( $data, $matching_schema, $context );
    2520         }
    2521     }
    2522 
    2523     if ( ! is_array( $data ) && ! is_object( $data ) ) {
    2524         return $data;
    2525     }
    2526 
    2527     if ( isset( $schema['type'] ) ) {
    2528         $type = $schema['type'];
    2529     } elseif ( isset( $schema['properties'] ) ) {
    2530         $type = 'object'; // Back compat if a developer accidentally omitted the type.
    2531     } else {
    2532         return $data;
    2533     }
    2534 
    2535     $is_array_type  = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) );
    2536     $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) );
    2537 
    2538     if ( $is_array_type && $is_object_type ) {
    2539         if ( rest_is_array( $data ) ) {
    2540             $is_object_type = false;
    2541         } else {
    2542             $is_array_type = false;
    2543         }
    2544     }
    2545 
    2546     $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] );
    2547 
    2548     foreach ( $data as $key => $value ) {
    2549         $check = array();
    2550 
    2551         if ( $is_array_type ) {
    2552             $check = isset( $schema['items'] ) ? $schema['items'] : array();
    2553         } elseif ( $is_object_type ) {
    2554             if ( isset( $schema['properties'][ $key ] ) ) {
    2555                 $check = $schema['properties'][ $key ];
    2556             } else {
    2557                 $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
    2558                 if ( null !== $pattern_property_schema ) {
    2559                     $check = $pattern_property_schema;
    2560                 } elseif ( $has_additional_properties ) {
    2561                     $check = $schema['additionalProperties'];
    2562                 }
    2563             }
    2564         }
    2565 
    2566         if ( ! isset( $check['context'] ) ) {
    2567             continue;
    2568         }
    2569 
    2570         if ( ! in_array( $context, $check['context'], true ) ) {
    2571             if ( $is_array_type ) {
    2572                 // All array items share schema, so there's no need to check each one.
    2573                 $data = array();
    2574                 break;
    2575             }
    2576 
    2577             if ( is_object( $data ) ) {
    2578                 unset( $data->$key );
    2579             } else {
    2580                 unset( $data[ $key ] );
    2581             }
    2582         } elseif ( is_array( $value ) || is_object( $value ) ) {
    2583             $new_value = rest_filter_response_by_context( $value, $check, $context );
    2584 
    2585             if ( is_object( $data ) ) {
    2586                 $data->$key = $new_value;
    2587             } else {
    2588                 $data[ $key ] = $new_value;
    2589             }
    2590         }
    2591     }
    2592 
    2593     return $data;
    2594 }
    2595 
    2596 /**
    2597  * Sets the "additionalProperties" to false by default for all object definitions in the schema.
    2598  *
    2599  * @since 5.5.0
    2600  * @since 5.6.0 Support the "patternProperties" keyword.
    2601  *
    2602  * @param array $schema The schema to modify.
    2603  * @return array The modified schema.
    2604  */
    2605 function rest_default_additional_properties_to_false( $schema ) {
    2606     $type = (array) $schema['type'];
    2607 
    2608     if ( in_array( 'object', $type, true ) ) {
    2609         if ( isset( $schema['properties'] ) ) {
    2610             foreach ( $schema['properties'] as $key => $child_schema ) {
    2611                 $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
    2612             }
    2613         }
    2614 
    2615         if ( isset( $schema['patternProperties'] ) ) {
    2616             foreach ( $schema['patternProperties'] as $key => $child_schema ) {
    2617                 $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
    2618             }
    2619         }
    2620 
    2621         if ( ! isset( $schema['additionalProperties'] ) ) {
    2622             $schema['additionalProperties'] = false;
    2623         }
    2624     }
    2625 
    2626     if ( in_array( 'array', $type, true ) ) {
    2627         if ( isset( $schema['items'] ) ) {
    2628             $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
    2629         }
    2630     }
    2631 
    2632     return $schema;
    2633 }
    2634 
    2635 /**
    2636  * Gets the REST API route for a post.
    2637  *
    2638  * @since 5.5.0
    2639  *
    2640  * @param int|WP_Post $post Post ID or post object.
    2641  * @return string The route path with a leading slash for the given post, or an empty string if there is not a route.
    2642  */
    2643 function rest_get_route_for_post( $post ) {
    2644     $post = get_post( $post );
    2645 
    2646     if ( ! $post instanceof WP_Post ) {
    2647         return '';
    2648     }
    2649 
    2650     $post_type = get_post_type_object( $post->post_type );
    2651     if ( ! $post_type ) {
    2652         return '';
    2653     }
    2654 
    2655     $controller = $post_type->get_rest_controller();
    2656     if ( ! $controller ) {
    2657         return '';
    2658     }
    2659 
    2660     $route = '';
    2661 
    2662     // The only two controllers that we can detect are the Attachments and Posts controllers.
    2663     if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) {
    2664         $namespace = 'wp/v2';
    2665         $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
    2666         $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID );
    2667     }
    2668 
    2669     /**
    2670      * Filters the REST API route for a post.
    2671      *
    2672      * @since 5.5.0
    2673      *
    2674      * @param string  $route The route path.
    2675      * @param WP_Post $post  The post object.
    2676      */
    2677     return apply_filters( 'rest_route_for_post', $route, $post );
    2678 }
    2679 
    2680 /**
    2681  * Gets the REST API route for a term.
    2682  *
    2683  * @since 5.5.0
    2684  *
    2685  * @param int|WP_Term $term Term ID or term object.
    2686  * @return string The route path with a leading slash for the given term, or an empty string if there is not a route.
    2687  */
    2688 function rest_get_route_for_term( $term ) {
    2689     $term = get_term( $term );
    2690 
    2691     if ( ! $term instanceof WP_Term ) {
    2692         return '';
    2693     }
    2694 
    2695     $taxonomy = get_taxonomy( $term->taxonomy );
    2696     if ( ! $taxonomy ) {
    2697         return '';
    2698     }
    2699 
    2700     $controller = $taxonomy->get_rest_controller();
    2701     if ( ! $controller ) {
    2702         return '';
    2703     }
    2704 
    2705     $route = '';
    2706 
    2707     // The only controller that works is the Terms controller.
    2708     if ( $controller instanceof WP_REST_Terms_Controller ) {
    2709         $namespace = 'wp/v2';
    2710         $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
    2711         $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
    2712     }
    2713 
    2714     /**
    2715      * Filters the REST API route for a term.
    2716      *
    2717      * @since 5.5.0
    2718      *
    2719      * @param string  $route The route path.
    2720      * @param WP_Term $term  The term object.
    2721      */
    2722     return apply_filters( 'rest_route_for_term', $route, $term );
    2723 }
    2724 
    2725 /**
    2726  * Gets the REST route for the currently queried object.
    2727  *
    2728  * @since 5.5.0
    2729  *
    2730  * @return string The REST route of the resource, or an empty string if no resource identified.
    2731  */
    2732 function rest_get_queried_resource_route() {
    2733     if ( is_singular() ) {
    2734         $route = rest_get_route_for_post( get_queried_object() );
    2735     } elseif ( is_category() || is_tag() || is_tax() ) {
    2736         $route = rest_get_route_for_term( get_queried_object() );
    2737     } elseif ( is_author() ) {
    2738         $route = '/wp/v2/users/' . get_queried_object_id();
    2739     } else {
    2740         $route = '';
    2741     }
    2742 
    2743     /**
    2744      * Filters the REST route for the currently queried object.
    2745      *
    2746      * @since 5.5.0
    2747      *
    2748      * @param string $link The route with a leading slash, or an empty string.
    2749      */
    2750     return apply_filters( 'rest_queried_resource_route', $route );
    2751 }
    2752 
    2753 /**
    2754  * Retrieves an array of endpoint arguments from the item schema and endpoint method.
     1878 * Get all valid JSON schema properties.
    27551879 *
    27561880 * @since 5.6.0
    27571881 *
    2758  * @param array  $schema The full JSON schema for the endpoint.
    2759  * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
    2760  *                       checked for required values and may fall-back to a given default, this is not done
    2761  *                       on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
    2762  * @return array The endpoint arguments.
    2763  */
    2764 function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
    2765 
    2766     $schema_properties       = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
    2767     $endpoint_args           = array();
    2768     $valid_schema_properties = array(
     1882 * @return string[] All valid JSON schema properties.
     1883 */
     1884function rest_get_allowed_schema_keywords() {
     1885    return array(
     1886        'title',
     1887        'description',
     1888        'default',
    27691889        'type',
    27701890        'format',
     
    27901910        'oneOf',
    27911911    );
     1912}
     1913
     1914/**
     1915 * Validate a value based on a schema.
     1916 *
     1917 * @since 4.7.0
     1918 * @since 4.9.0 Support the "object" type.
     1919 * @since 5.2.0 Support validating "additionalProperties" against a schema.
     1920 * @since 5.3.0 Support multiple types.
     1921 * @since 5.4.0 Convert an empty string to an empty object.
     1922 * @since 5.5.0 Add the "uuid" and "hex-color" formats.
     1923 *              Support the "minLength", "maxLength" and "pattern" keywords for strings.
     1924 *              Support the "minItems", "maxItems" and "uniqueItems" keywords for arrays.
     1925 *              Validate required properties.
     1926 * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
     1927 *              Support the "multipleOf" keyword for numbers and integers.
     1928 *              Support the "patternProperties" keyword for objects.
     1929 *              Support the "anyOf" and "oneOf" keywords.
     1930 *
     1931 * @param mixed  $value The value to validate.
     1932 * @param array  $args  Schema array to use for validation.
     1933 * @param string $param The parameter name, used in error messages.
     1934 * @return true|WP_Error
     1935 */
     1936function rest_validate_value_from_schema( $value, $args, $param = '' ) {
     1937    if ( isset( $args['anyOf'] ) ) {
     1938        $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
     1939        if ( is_wp_error( $matching_schema ) ) {
     1940            return $matching_schema;
     1941        }
     1942
     1943        if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
     1944            $args['type'] = $matching_schema['type'];
     1945        }
     1946    }
     1947
     1948    if ( isset( $args['oneOf'] ) ) {
     1949        $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
     1950        if ( is_wp_error( $matching_schema ) ) {
     1951            return $matching_schema;
     1952        }
     1953
     1954        if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
     1955            $args['type'] = $matching_schema['type'];
     1956        }
     1957    }
     1958
     1959    $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
     1960
     1961    if ( ! isset( $args['type'] ) ) {
     1962        /* translators: %s: Parameter. */
     1963        _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
     1964    }
     1965
     1966    if ( is_array( $args['type'] ) ) {
     1967        $best_type = rest_handle_multi_type_schema( $value, $args, $param );
     1968
     1969        if ( ! $best_type ) {
     1970            return new WP_Error(
     1971                'rest_invalid_type',
     1972                /* translators: 1: Parameter, 2: List of types. */
     1973                sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
     1974                array( 'param' => $param )
     1975            );
     1976        }
     1977
     1978        $args['type'] = $best_type;
     1979    }
     1980
     1981    if ( ! in_array( $args['type'], $allowed_types, true ) ) {
     1982        _doing_it_wrong(
     1983            __FUNCTION__,
     1984            /* translators: 1: Parameter, 2: The list of allowed types. */
     1985            wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
     1986            '5.5.0'
     1987        );
     1988    }
     1989
     1990    if ( 'array' === $args['type'] ) {
     1991        if ( ! rest_is_array( $value ) ) {
     1992            return new WP_Error(
     1993                'rest_invalid_type',
     1994                /* translators: 1: Parameter, 2: Type name. */
     1995                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
     1996                array( 'param' => $param )
     1997            );
     1998        }
     1999
     2000        $value = rest_sanitize_array( $value );
     2001
     2002        if ( isset( $args['items'] ) ) {
     2003            foreach ( $value as $index => $v ) {
     2004                $is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
     2005                if ( is_wp_error( $is_valid ) ) {
     2006                    return $is_valid;
     2007                }
     2008            }
     2009        }
     2010
     2011        if ( isset( $args['minItems'] ) && count( $value ) < $args['minItems'] ) {
     2012            /* translators: 1: Parameter, 2: Number. */
     2013            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s items.' ), $param, number_format_i18n( $args['minItems'] ) ) );
     2014        }
     2015
     2016        if ( isset( $args['maxItems'] ) && count( $value ) > $args['maxItems'] ) {
     2017            /* translators: 1: Parameter, 2: Number. */
     2018            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s items.' ), $param, number_format_i18n( $args['maxItems'] ) ) );
     2019        }
     2020
     2021        if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
     2022            /* translators: 1: Parameter. */
     2023            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
     2024        }
     2025    }
     2026
     2027    if ( 'object' === $args['type'] ) {
     2028        if ( ! rest_is_object( $value ) ) {
     2029            return new WP_Error(
     2030                'rest_invalid_type',
     2031                /* translators: 1: Parameter, 2: Type name. */
     2032                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
     2033                array( 'param' => $param )
     2034            );
     2035        }
     2036
     2037        $value = rest_sanitize_object( $value );
     2038
     2039        if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
     2040            foreach ( $args['required'] as $name ) {
     2041                if ( ! array_key_exists( $name, $value ) ) {
     2042                    /* translators: 1: Property of an object, 2: Parameter. */
     2043                    return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
     2044                }
     2045            }
     2046        } elseif ( isset( $args['properties'] ) ) { // schema version 3
     2047            foreach ( $args['properties'] as $name => $property ) {
     2048                if ( isset( $property['required'] ) && true === $property['required'] && ! array_key_exists( $name, $value ) ) {
     2049                    /* translators: 1: Property of an object, 2: Parameter. */
     2050                    return new WP_Error( 'rest_property_required', sprintf( __( '%1$s is a required property of %2$s.' ), $name, $param ) );
     2051                }
     2052            }
     2053        }
     2054
     2055        foreach ( $value as $property => $v ) {
     2056            if ( isset( $args['properties'][ $property ] ) ) {
     2057                $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
     2058                if ( is_wp_error( $is_valid ) ) {
     2059                    return $is_valid;
     2060                }
     2061                continue;
     2062            }
     2063
     2064            $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
     2065            if ( null !== $pattern_property_schema ) {
     2066                $is_valid = rest_validate_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
     2067                if ( is_wp_error( $is_valid ) ) {
     2068                    return $is_valid;
     2069                }
     2070                continue;
     2071            }
     2072
     2073            if ( isset( $args['additionalProperties'] ) ) {
     2074                if ( false === $args['additionalProperties'] ) {
     2075                    /* translators: %s: Property of an object. */
     2076                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) );
     2077                }
     2078
     2079                if ( is_array( $args['additionalProperties'] ) ) {
     2080                    $is_valid = rest_validate_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
     2081                    if ( is_wp_error( $is_valid ) ) {
     2082                        return $is_valid;
     2083                    }
     2084                }
     2085            }
     2086        }
     2087
     2088        if ( isset( $args['minProperties'] ) && count( $value ) < $args['minProperties'] ) {
     2089            /* translators: 1: Parameter, 2: Number. */
     2090            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at least %2$s properties.' ), $param, number_format_i18n( $args['minProperties'] ) ) );
     2091        }
     2092
     2093        if ( isset( $args['maxProperties'] ) && count( $value ) > $args['maxProperties'] ) {
     2094            /* translators: 1: Parameter, 2: Number. */
     2095            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must contain at most %2$s properties.' ), $param, number_format_i18n( $args['maxProperties'] ) ) );
     2096        }
     2097    }
     2098
     2099    if ( 'null' === $args['type'] ) {
     2100        if ( null !== $value ) {
     2101            return new WP_Error(
     2102                'rest_invalid_type',
     2103                /* translators: 1: Parameter, 2: Type name. */
     2104                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
     2105                array( 'param' => $param )
     2106            );
     2107        }
     2108
     2109        return true;
     2110    }
     2111
     2112    if ( ! empty( $args['enum'] ) ) {
     2113        if ( ! in_array( $value, $args['enum'], true ) ) {
     2114            /* translators: 1: Parameter, 2: List of valid values. */
     2115            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
     2116        }
     2117    }
     2118
     2119    if ( in_array( $args['type'], array( 'integer', 'number' ), true ) ) {
     2120        if ( ! is_numeric( $value ) ) {
     2121            return new WP_Error(
     2122                'rest_invalid_type',
     2123                /* translators: 1: Parameter, 2: Type name. */
     2124                sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
     2125                array( 'param' => $param )
     2126            );
     2127        }
     2128
     2129        if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
     2130            /* translators: 1: Parameter, 2: Multiplier. */
     2131            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be a multiple of %2$s.' ), $param, $args['multipleOf'] ) );
     2132        }
     2133    }
     2134
     2135    if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
     2136        return new WP_Error(
     2137            'rest_invalid_type',
     2138            /* translators: 1: Parameter, 2: Type name. */
     2139            sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
     2140            array( 'param' => $param )
     2141        );
     2142    }
     2143
     2144    if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
     2145        return new WP_Error(
     2146            'rest_invalid_type',
     2147            /* translators: 1: Parameter, 2: Type name. */
     2148            sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
     2149            array( 'param' => $param )
     2150        );
     2151    }
     2152
     2153    if ( 'string' === $args['type'] ) {
     2154        if ( ! is_string( $value ) ) {
     2155            return new WP_Error(
     2156                'rest_invalid_type',
     2157                /* translators: 1: Parameter, 2: Type name. */
     2158                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
     2159                array( 'param' => $param )
     2160            );
     2161        }
     2162
     2163        if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
     2164            return new WP_Error(
     2165                'rest_invalid_param',
     2166                sprintf(
     2167                    /* translators: 1: Parameter, 2: Number of characters. */
     2168                    _n( '%1$s must be at least %2$s character long.', '%1$s must be at least %2$s characters long.', $args['minLength'] ),
     2169                    $param,
     2170                    number_format_i18n( $args['minLength'] )
     2171                )
     2172            );
     2173        }
     2174
     2175        if ( isset( $args['maxLength'] ) && mb_strlen( $value ) > $args['maxLength'] ) {
     2176            return new WP_Error(
     2177                'rest_invalid_param',
     2178                sprintf(
     2179                    /* translators: 1: Parameter, 2: Number of characters. */
     2180                    _n( '%1$s must be at most %2$s character long.', '%1$s must be at most %2$s characters long.', $args['maxLength'] ),
     2181                    $param,
     2182                    number_format_i18n( $args['maxLength'] )
     2183                )
     2184            );
     2185        }
     2186
     2187        if ( isset( $args['pattern'] ) && ! rest_validate_json_schema_pattern( $args['pattern'], $value ) ) {
     2188            /* translators: 1: Parameter, 2: Pattern. */
     2189            return new WP_Error( 'rest_invalid_pattern', sprintf( __( '%1$s does not match pattern %2$s.' ), $param, $args['pattern'] ) );
     2190        }
     2191    }
     2192
     2193    // The "format" keyword should only be applied to strings. However, for backward compatibility,
     2194    // we allow the "format" keyword if the type keyword was not specified, or was set to an invalid value.
     2195    if ( isset( $args['format'] )
     2196        && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
     2197    ) {
     2198        switch ( $args['format'] ) {
     2199            case 'hex-color':
     2200                if ( ! rest_parse_hex_color( $value ) ) {
     2201                    return new WP_Error( 'rest_invalid_hex_color', __( 'Invalid hex color.' ) );
     2202                }
     2203                break;
     2204
     2205            case 'date-time':
     2206                if ( ! rest_parse_date( $value ) ) {
     2207                    return new WP_Error( 'rest_invalid_date', __( 'Invalid date.' ) );
     2208                }
     2209                break;
     2210
     2211            case 'email':
     2212                if ( ! is_email( $value ) ) {
     2213                    return new WP_Error( 'rest_invalid_email', __( 'Invalid email address.' ) );
     2214                }
     2215                break;
     2216            case 'ip':
     2217                if ( ! rest_is_ip_address( $value ) ) {
     2218                    /* translators: %s: IP address. */
     2219                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $param ) );
     2220                }
     2221                break;
     2222            case 'uuid':
     2223                if ( ! wp_is_uuid( $value ) ) {
     2224                    /* translators: %s: The name of a JSON field expecting a valid UUID. */
     2225                    return new WP_Error( 'rest_invalid_uuid', sprintf( __( '%s is not a valid UUID.' ), $param ) );
     2226                }
     2227                break;
     2228        }
     2229    }
     2230
     2231    if ( in_array( $args['type'], array( 'number', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
     2232        if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
     2233            if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
     2234                /* translators: 1: Parameter, 2: Minimum number. */
     2235                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d' ), $param, $args['minimum'] ) );
     2236            } elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
     2237                /* translators: 1: Parameter, 2: Minimum number. */
     2238                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than or equal to %2$d' ), $param, $args['minimum'] ) );
     2239            }
     2240        } elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
     2241            if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
     2242                /* translators: 1: Parameter, 2: Maximum number. */
     2243                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d' ), $param, $args['maximum'] ) );
     2244            } elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
     2245                /* translators: 1: Parameter, 2: Maximum number. */
     2246                return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than or equal to %2$d' ), $param, $args['maximum'] ) );
     2247            }
     2248        } elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
     2249            if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
     2250                if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
     2251                    /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
     2252                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
     2253                }
     2254            } elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
     2255                if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
     2256                    /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
     2257                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
     2258                }
     2259            } elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
     2260                if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
     2261                    /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
     2262                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
     2263                }
     2264            } elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
     2265                if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
     2266                    /* translators: 1: Parameter, 2: Minimum number, 3: Maximum number. */
     2267                    return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
     2268                }
     2269            }
     2270        }
     2271    }
     2272
     2273    return true;
     2274}
     2275
     2276/**
     2277 * Sanitize a value based on a schema.
     2278 *
     2279 * @since 4.7.0
     2280 * @since 5.5.0 Added the `$param` parameter.
     2281 * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
     2282 *
     2283 * @param mixed  $value The value to sanitize.
     2284 * @param array  $args  Schema array to use for sanitization.
     2285 * @param string $param The parameter name, used in error messages.
     2286 * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
     2287 */
     2288function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
     2289    if ( isset( $args['anyOf'] ) ) {
     2290        $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
     2291        if ( is_wp_error( $matching_schema ) ) {
     2292            return $matching_schema;
     2293        }
     2294
     2295        if ( ! isset( $args['type'] ) ) {
     2296            $args['type'] = $matching_schema['type'];
     2297        }
     2298
     2299        $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
     2300    }
     2301
     2302    if ( isset( $args['oneOf'] ) ) {
     2303        $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
     2304        if ( is_wp_error( $matching_schema ) ) {
     2305            return $matching_schema;
     2306        }
     2307
     2308        if ( ! isset( $args['type'] ) ) {
     2309            $args['type'] = $matching_schema['type'];
     2310        }
     2311
     2312        $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
     2313    }
     2314
     2315    $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
     2316
     2317    if ( ! isset( $args['type'] ) ) {
     2318        /* translators: %s: Parameter. */
     2319        _doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
     2320    }
     2321
     2322    if ( is_array( $args['type'] ) ) {
     2323        $best_type = rest_handle_multi_type_schema( $value, $args, $param );
     2324
     2325        if ( ! $best_type ) {
     2326            return null;
     2327        }
     2328
     2329        $args['type'] = $best_type;
     2330    }
     2331
     2332    if ( ! in_array( $args['type'], $allowed_types, true ) ) {
     2333        _doing_it_wrong(
     2334            __FUNCTION__,
     2335            /* translators: 1: Parameter, 2: The list of allowed types. */
     2336            wp_sprintf( __( 'The "type" schema keyword for %1$s can only be one of the built-in types: %2$l.' ), $param, $allowed_types ),
     2337            '5.5.0'
     2338        );
     2339    }
     2340
     2341    if ( 'array' === $args['type'] ) {
     2342        $value = rest_sanitize_array( $value );
     2343
     2344        if ( ! empty( $args['items'] ) ) {
     2345            foreach ( $value as $index => $v ) {
     2346                $value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
     2347            }
     2348        }
     2349
     2350        if ( ! empty( $args['uniqueItems'] ) && ! rest_validate_array_contains_unique_items( $value ) ) {
     2351            /* translators: 1: Parameter. */
     2352            return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s has duplicate items.' ), $param ) );
     2353        }
     2354
     2355        return $value;
     2356    }
     2357
     2358    if ( 'object' === $args['type'] ) {
     2359        $value = rest_sanitize_object( $value );
     2360
     2361        foreach ( $value as $property => $v ) {
     2362            if ( isset( $args['properties'][ $property ] ) ) {
     2363                $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
     2364                continue;
     2365            }
     2366
     2367            $pattern_property_schema = rest_find_matching_pattern_property_schema( $property, $args );
     2368            if ( null !== $pattern_property_schema ) {
     2369                $value[ $property ] = rest_sanitize_value_from_schema( $v, $pattern_property_schema, $param . '[' . $property . ']' );
     2370                continue;
     2371            }
     2372
     2373            if ( isset( $args['additionalProperties'] ) ) {
     2374                if ( false === $args['additionalProperties'] ) {
     2375                    unset( $value[ $property ] );
     2376                } elseif ( is_array( $args['additionalProperties'] ) ) {
     2377                    $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
     2378                }
     2379            }
     2380        }
     2381
     2382        return $value;
     2383    }
     2384
     2385    if ( 'null' === $args['type'] ) {
     2386        return null;
     2387    }
     2388
     2389    if ( 'integer' === $args['type'] ) {
     2390        return (int) $value;
     2391    }
     2392
     2393    if ( 'number' === $args['type'] ) {
     2394        return (float) $value;
     2395    }
     2396
     2397    if ( 'boolean' === $args['type'] ) {
     2398        return rest_sanitize_boolean( $value );
     2399    }
     2400
     2401    // This behavior matches rest_validate_value_from_schema().
     2402    if ( isset( $args['format'] )
     2403        && ( ! isset( $args['type'] ) || 'string' === $args['type'] || ! in_array( $args['type'], $allowed_types, true ) )
     2404    ) {
     2405        switch ( $args['format'] ) {
     2406            case 'hex-color':
     2407                return (string) sanitize_hex_color( $value );
     2408
     2409            case 'date-time':
     2410                return sanitize_text_field( $value );
     2411
     2412            case 'email':
     2413                // sanitize_email() validates, which would be unexpected.
     2414                return sanitize_text_field( $value );
     2415
     2416            case 'uri':
     2417                return esc_url_raw( $value );
     2418
     2419            case 'ip':
     2420                return sanitize_text_field( $value );
     2421
     2422            case 'uuid':
     2423                return sanitize_text_field( $value );
     2424        }
     2425    }
     2426
     2427    if ( 'string' === $args['type'] ) {
     2428        return (string) $value;
     2429    }
     2430
     2431    return $value;
     2432}
     2433
     2434/**
     2435 * Append result of internal request to REST API for purpose of preloading data to be attached to a page.
     2436 * Expected to be called in the context of `array_reduce`.
     2437 *
     2438 * @since 5.0.0
     2439 *
     2440 * @param array  $memo Reduce accumulator.
     2441 * @param string $path REST API path to preload.
     2442 * @return array Modified reduce accumulator.
     2443 */
     2444function rest_preload_api_request( $memo, $path ) {
     2445    // array_reduce() doesn't support passing an array in PHP 5.2,
     2446    // so we need to make sure we start with one.
     2447    if ( ! is_array( $memo ) ) {
     2448        $memo = array();
     2449    }
     2450
     2451    if ( empty( $path ) ) {
     2452        return $memo;
     2453    }
     2454
     2455    $method = 'GET';
     2456    if ( is_array( $path ) && 2 === count( $path ) ) {
     2457        $method = end( $path );
     2458        $path   = reset( $path );
     2459
     2460        if ( ! in_array( $method, array( 'GET', 'OPTIONS' ), true ) ) {
     2461            $method = 'GET';
     2462        }
     2463    }
     2464
     2465    $path_parts = parse_url( $path );
     2466    if ( false === $path_parts ) {
     2467        return $memo;
     2468    }
     2469
     2470    $request = new WP_REST_Request( $method, $path_parts['path'] );
     2471    if ( ! empty( $path_parts['query'] ) ) {
     2472        parse_str( $path_parts['query'], $query_params );
     2473        $request->set_query_params( $query_params );
     2474    }
     2475
     2476    $response = rest_do_request( $request );
     2477    if ( 200 === $response->status ) {
     2478        $server = rest_get_server();
     2479        $data   = (array) $response->get_data();
     2480        $links  = $server::get_compact_response_links( $response );
     2481        if ( ! empty( $links ) ) {
     2482            $data['_links'] = $links;
     2483        }
     2484
     2485        if ( 'OPTIONS' === $method ) {
     2486            $response = rest_send_allow_header( $response, $server, $request );
     2487
     2488            $memo[ $method ][ $path ] = array(
     2489                'body'    => $data,
     2490                'headers' => $response->headers,
     2491            );
     2492        } else {
     2493            $memo[ $path ] = array(
     2494                'body'    => $data,
     2495                'headers' => $response->headers,
     2496            );
     2497        }
     2498    }
     2499
     2500    return $memo;
     2501}
     2502
     2503/**
     2504 * Parses the "_embed" parameter into the list of resources to embed.
     2505 *
     2506 * @since 5.4.0
     2507 *
     2508 * @param string|array $embed Raw "_embed" parameter value.
     2509 * @return true|string[] Either true to embed all embeds, or a list of relations to embed.
     2510 */
     2511function rest_parse_embed_param( $embed ) {
     2512    if ( ! $embed || 'true' === $embed || '1' === $embed ) {
     2513        return true;
     2514    }
     2515
     2516    $rels = wp_parse_list( $embed );
     2517
     2518    if ( ! $rels ) {
     2519        return true;
     2520    }
     2521
     2522    return $rels;
     2523}
     2524
     2525/**
     2526 * Filters the response to remove any fields not available in the given context.
     2527 *
     2528 * @since 5.5.0
     2529 * @since 5.6.0 Support the "patternProperties" keyword for objects.
     2530 *              Support the "anyOf" and "oneOf" keywords.
     2531 *
     2532 * @param array|object $data    The response data to modify.
     2533 * @param array        $schema  The schema for the endpoint used to filter the response.
     2534 * @param string       $context The requested context.
     2535 * @return array|object The filtered response data.
     2536 */
     2537function rest_filter_response_by_context( $data, $schema, $context ) {
     2538    if ( isset( $schema['anyOf'] ) ) {
     2539        $matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
     2540        if ( ! is_wp_error( $matching_schema ) ) {
     2541            if ( ! isset( $schema['type'] ) ) {
     2542                $schema['type'] = $matching_schema['type'];
     2543            }
     2544
     2545            $data = rest_filter_response_by_context( $data, $matching_schema, $context );
     2546        }
     2547    }
     2548
     2549    if ( isset( $schema['oneOf'] ) ) {
     2550        $matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
     2551        if ( ! is_wp_error( $matching_schema ) ) {
     2552            if ( ! isset( $schema['type'] ) ) {
     2553                $schema['type'] = $matching_schema['type'];
     2554            }
     2555
     2556            $data = rest_filter_response_by_context( $data, $matching_schema, $context );
     2557        }
     2558    }
     2559
     2560    if ( ! is_array( $data ) && ! is_object( $data ) ) {
     2561        return $data;
     2562    }
     2563
     2564    if ( isset( $schema['type'] ) ) {
     2565        $type = $schema['type'];
     2566    } elseif ( isset( $schema['properties'] ) ) {
     2567        $type = 'object'; // Back compat if a developer accidentally omitted the type.
     2568    } else {
     2569        return $data;
     2570    }
     2571
     2572    $is_array_type  = 'array' === $type || ( is_array( $type ) && in_array( 'array', $type, true ) );
     2573    $is_object_type = 'object' === $type || ( is_array( $type ) && in_array( 'object', $type, true ) );
     2574
     2575    if ( $is_array_type && $is_object_type ) {
     2576        if ( rest_is_array( $data ) ) {
     2577            $is_object_type = false;
     2578        } else {
     2579            $is_array_type = false;
     2580        }
     2581    }
     2582
     2583    $has_additional_properties = $is_object_type && isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] );
     2584
     2585    foreach ( $data as $key => $value ) {
     2586        $check = array();
     2587
     2588        if ( $is_array_type ) {
     2589            $check = isset( $schema['items'] ) ? $schema['items'] : array();
     2590        } elseif ( $is_object_type ) {
     2591            if ( isset( $schema['properties'][ $key ] ) ) {
     2592                $check = $schema['properties'][ $key ];
     2593            } else {
     2594                $pattern_property_schema = rest_find_matching_pattern_property_schema( $key, $schema );
     2595                if ( null !== $pattern_property_schema ) {
     2596                    $check = $pattern_property_schema;
     2597                } elseif ( $has_additional_properties ) {
     2598                    $check = $schema['additionalProperties'];
     2599                }
     2600            }
     2601        }
     2602
     2603        if ( ! isset( $check['context'] ) ) {
     2604            continue;
     2605        }
     2606
     2607        if ( ! in_array( $context, $check['context'], true ) ) {
     2608            if ( $is_array_type ) {
     2609                // All array items share schema, so there's no need to check each one.
     2610                $data = array();
     2611                break;
     2612            }
     2613
     2614            if ( is_object( $data ) ) {
     2615                unset( $data->$key );
     2616            } else {
     2617                unset( $data[ $key ] );
     2618            }
     2619        } elseif ( is_array( $value ) || is_object( $value ) ) {
     2620            $new_value = rest_filter_response_by_context( $value, $check, $context );
     2621
     2622            if ( is_object( $data ) ) {
     2623                $data->$key = $new_value;
     2624            } else {
     2625                $data[ $key ] = $new_value;
     2626            }
     2627        }
     2628    }
     2629
     2630    return $data;
     2631}
     2632
     2633/**
     2634 * Sets the "additionalProperties" to false by default for all object definitions in the schema.
     2635 *
     2636 * @since 5.5.0
     2637 * @since 5.6.0 Support the "patternProperties" keyword.
     2638 *
     2639 * @param array $schema The schema to modify.
     2640 * @return array The modified schema.
     2641 */
     2642function rest_default_additional_properties_to_false( $schema ) {
     2643    $type = (array) $schema['type'];
     2644
     2645    if ( in_array( 'object', $type, true ) ) {
     2646        if ( isset( $schema['properties'] ) ) {
     2647            foreach ( $schema['properties'] as $key => $child_schema ) {
     2648                $schema['properties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
     2649            }
     2650        }
     2651
     2652        if ( isset( $schema['patternProperties'] ) ) {
     2653            foreach ( $schema['patternProperties'] as $key => $child_schema ) {
     2654                $schema['patternProperties'][ $key ] = rest_default_additional_properties_to_false( $child_schema );
     2655            }
     2656        }
     2657
     2658        if ( ! isset( $schema['additionalProperties'] ) ) {
     2659            $schema['additionalProperties'] = false;
     2660        }
     2661    }
     2662
     2663    if ( in_array( 'array', $type, true ) ) {
     2664        if ( isset( $schema['items'] ) ) {
     2665            $schema['items'] = rest_default_additional_properties_to_false( $schema['items'] );
     2666        }
     2667    }
     2668
     2669    return $schema;
     2670}
     2671
     2672/**
     2673 * Gets the REST API route for a post.
     2674 *
     2675 * @since 5.5.0
     2676 *
     2677 * @param int|WP_Post $post Post ID or post object.
     2678 * @return string The route path with a leading slash for the given post, or an empty string if there is not a route.
     2679 */
     2680function rest_get_route_for_post( $post ) {
     2681    $post = get_post( $post );
     2682
     2683    if ( ! $post instanceof WP_Post ) {
     2684        return '';
     2685    }
     2686
     2687    $post_type = get_post_type_object( $post->post_type );
     2688    if ( ! $post_type ) {
     2689        return '';
     2690    }
     2691
     2692    $controller = $post_type->get_rest_controller();
     2693    if ( ! $controller ) {
     2694        return '';
     2695    }
     2696
     2697    $route = '';
     2698
     2699    // The only two controllers that we can detect are the Attachments and Posts controllers.
     2700    if ( in_array( get_class( $controller ), array( 'WP_REST_Attachments_Controller', 'WP_REST_Posts_Controller' ), true ) ) {
     2701        $namespace = 'wp/v2';
     2702        $rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
     2703        $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $post->ID );
     2704    }
     2705
     2706    /**
     2707     * Filters the REST API route for a post.
     2708     *
     2709     * @since 5.5.0
     2710     *
     2711     * @param string  $route The route path.
     2712     * @param WP_Post $post  The post object.
     2713     */
     2714    return apply_filters( 'rest_route_for_post', $route, $post );
     2715}
     2716
     2717/**
     2718 * Gets the REST API route for a term.
     2719 *
     2720 * @since 5.5.0
     2721 *
     2722 * @param int|WP_Term $term Term ID or term object.
     2723 * @return string The route path with a leading slash for the given term, or an empty string if there is not a route.
     2724 */
     2725function rest_get_route_for_term( $term ) {
     2726    $term = get_term( $term );
     2727
     2728    if ( ! $term instanceof WP_Term ) {
     2729        return '';
     2730    }
     2731
     2732    $taxonomy = get_taxonomy( $term->taxonomy );
     2733    if ( ! $taxonomy ) {
     2734        return '';
     2735    }
     2736
     2737    $controller = $taxonomy->get_rest_controller();
     2738    if ( ! $controller ) {
     2739        return '';
     2740    }
     2741
     2742    $route = '';
     2743
     2744    // The only controller that works is the Terms controller.
     2745    if ( $controller instanceof WP_REST_Terms_Controller ) {
     2746        $namespace = 'wp/v2';
     2747        $rest_base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
     2748        $route     = sprintf( '/%s/%s/%d', $namespace, $rest_base, $term->term_id );
     2749    }
     2750
     2751    /**
     2752     * Filters the REST API route for a term.
     2753     *
     2754     * @since 5.5.0
     2755     *
     2756     * @param string  $route The route path.
     2757     * @param WP_Term $term  The term object.
     2758     */
     2759    return apply_filters( 'rest_route_for_term', $route, $term );
     2760}
     2761
     2762/**
     2763 * Gets the REST route for the currently queried object.
     2764 *
     2765 * @since 5.5.0
     2766 *
     2767 * @return string The REST route of the resource, or an empty string if no resource identified.
     2768 */
     2769function rest_get_queried_resource_route() {
     2770    if ( is_singular() ) {
     2771        $route = rest_get_route_for_post( get_queried_object() );
     2772    } elseif ( is_category() || is_tag() || is_tax() ) {
     2773        $route = rest_get_route_for_term( get_queried_object() );
     2774    } elseif ( is_author() ) {
     2775        $route = '/wp/v2/users/' . get_queried_object_id();
     2776    } else {
     2777        $route = '';
     2778    }
     2779
     2780    /**
     2781     * Filters the REST route for the currently queried object.
     2782     *
     2783     * @since 5.5.0
     2784     *
     2785     * @param string $link The route with a leading slash, or an empty string.
     2786     */
     2787    return apply_filters( 'rest_queried_resource_route', $route );
     2788}
     2789
     2790/**
     2791 * Retrieves an array of endpoint arguments from the item schema and endpoint method.
     2792 *
     2793 * @since 5.6.0
     2794 *
     2795 * @param array  $schema The full JSON schema for the endpoint.
     2796 * @param string $method Optional. HTTP method of the endpoint. The arguments for `CREATABLE` endpoints are
     2797 *                       checked for required values and may fall-back to a given default, this is not done
     2798 *                       on `EDITABLE` endpoints. Default WP_REST_Server::CREATABLE.
     2799 * @return array The endpoint arguments.
     2800 */
     2801function rest_get_endpoint_args_for_schema( $schema, $method = WP_REST_Server::CREATABLE ) {
     2802
     2803    $schema_properties       = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
     2804    $endpoint_args           = array();
     2805    $valid_schema_properties = rest_get_allowed_schema_keywords();
     2806    $valid_schema_properties = array_diff( $valid_schema_properties, array( 'default', 'required' ) );
    27922807
    27932808    foreach ( $schema_properties as $field_id => $params ) {
     
    28022817            'sanitize_callback' => 'rest_sanitize_request_arg',
    28032818        );
    2804 
    2805         if ( isset( $params['description'] ) ) {
    2806             $endpoint_args[ $field_id ]['description'] = $params['description'];
    2807         }
    28082819
    28092820        if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
Note: See TracChangeset for help on using the changeset viewer.