Make WordPress Core

Ticket #38323: 38323.8.register-meta.diff

File 38323.8.register-meta.diff, 37.8 KB (added by tharsheblows, 5 years ago)

with tests

  • src/wp-includes/capabilities.php

     
    281281                        list( $_, $object_type, $_ ) = explode( '_', $cap );
    282282                        $object_id                   = (int) $args[0];
    283283
    284                         switch ( $object_type ) {
    285                                 case 'post':
    286                                         $post = get_post( $object_id );
    287                                         if ( ! $post ) {
    288                                                 break;
    289                                         }
     284                        $object_subtype = get_object_subtype( $object_type, $object_id );
    290285
    291                                         $sub_type = get_post_type( $post );
    292                                         break;
    293 
    294                                 case 'comment':
    295                                         $comment = get_comment( $object_id );
    296                                         if ( ! $comment ) {
    297                                                 break;
    298                                         }
    299 
    300                                         $sub_type = empty( $comment->comment_type ) ? 'comment' : $comment->comment_type;
    301                                         break;
    302 
    303                                 case 'term':
    304                                         $term = get_term( $object_id );
    305                                         if ( ! $term instanceof WP_Term ) {
    306                                                 break;
    307                                         }
    308 
    309                                         $sub_type = $term->taxonomy;
    310                                         break;
    311 
    312                                 case 'user':
    313                                         $user = get_user_by( 'id', $object_id );
    314                                         if ( ! $user ) {
    315                                                 break;
    316                                         }
    317 
    318                                         $sub_type = 'user';
    319                                         break;
    320                         }
    321 
    322                         if ( empty( $sub_type ) ) {
     286                        if ( empty( $object_subtype ) ) {
    323287                                $caps[] = 'do_not_allow';
    324288                                break;
    325289                        }
     
    328292
    329293                        $meta_key = isset( $args[1] ) ? $args[1] : false;
    330294
    331                         $has_filter = has_filter( "auth_{$object_type}_meta_{$meta_key}" ) || has_filter( "auth_{$object_type}_{$sub_type}_meta_{$meta_key}" );
    332                         if ( $meta_key && $has_filter ) {
     295                        if ( $meta_key ) {
     296                                $allowed = ! is_protected_meta( $meta_key, $object_type );
    333297
    334                                 /**
    335                                  * Filters whether the user is allowed to edit meta for specific object types.
    336                                  *
    337                                  * Return true to have the mapped meta caps from `edit_{$object_type}` apply.
    338                                  *
    339                                  * The dynamic portion of the hook name, `$object_type` refers to the object type being filtered.
    340                                  * The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap().
    341                                  *
    342                                  * @since 3.3.0 As `auth_post_meta_{$meta_key}`.
    343                                  * @since 4.6.0
    344                                  *
    345                                  * @param bool     $allowed   Whether the user can add the object meta. Default false.
    346                                  * @param string   $meta_key  The meta key.
    347                                  * @param int      $object_id Object ID.
    348                                  * @param int      $user_id   User ID.
    349                                  * @param string   $cap       Capability name.
    350                                  * @param string[] $caps      Array of the user's capabilities.
    351                                  */
    352                                 $allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}", false, $meta_key, $object_id, $user_id, $cap, $caps );
     298                                if ( ! empty( $object_subtype ) && has_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) {
    353299
    354                                 /**
    355                                  * Filters whether the user is allowed to edit meta for specific object types/subtypes.
    356                                  *
    357                                  * Return true to have the mapped meta caps from `edit_{$object_type}` apply.
    358                                  *
    359                                  * The dynamic portion of the hook name, `$object_type` refers to the object type being filtered.
    360                                  * The dynamic portion of the hook name, `$sub_type` refers to the object subtype being filtered.
    361                                  * The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap().
    362                                  *
    363                                  * @since 4.6.0 As `auth_post_{$post_type}_meta_{$meta_key}`.
    364                                  * @since 4.7.0
    365                                  *
    366                                  * @param bool     $allowed   Whether the user can add the object meta. Default false.
    367                                  * @param string   $meta_key  The meta key.
    368                                  * @param int      $object_id Object ID.
    369                                  * @param int      $user_id   User ID.
    370                                  * @param string   $cap       Capability name.
    371                                  * @param string[] $caps      Array of the user's capabilities.
    372                                  */
    373                                 $allowed = apply_filters( "auth_{$object_type}_{$sub_type}_meta_{$meta_key}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps );
     300                                        /**
     301                                         * Filters whether the user is allowed to edit a specific meta key of a specific object type and subtype.
     302                                         *
     303                                         * The dynamic portions of the hook name, `$object_type`, `$meta_key`,
     304                                         * and `$object_subtype`, refer to the metadata object type (comment, post, term or user),
     305                                         * the meta key value, and the object subtype respectively.
     306                                         *
     307                                         * @since 5.0.0
     308                                         *
     309                                         * @param bool     $allowed   Whether the user can add the object meta. Default false.
     310                                         * @param string   $meta_key  The meta key.
     311                                         * @param int      $object_id Object ID.
     312                                         * @param int      $user_id   User ID.
     313                                         * @param string   $cap       Capability name.
     314                                         * @param string[] $caps      Array of the user's capabilities.
     315                                         */
     316                                        $allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps );
     317                                } else {
    374318
     319                                        /**
     320                                         * Filters whether the user is allowed to edit a specific meta key of a specific object type.
     321                                         *
     322                                         * Return true to have the mapped meta caps from `edit_{$object_type}` apply.
     323                                         *
     324                                         * The dynamic portion of the hook name, `$object_type` refers to the object type being filtered.
     325                                         * The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap().
     326                                         *
     327                                         * @since 3.3.0 As `auth_post_meta_{$meta_key}`.
     328                                         * @since 4.6.0
     329                                         *
     330                                         * @param bool     $allowed   Whether the user can add the object meta. Default false.
     331                                         * @param string   $meta_key  The meta key.
     332                                         * @param int      $object_id Object ID.
     333                                         * @param int      $user_id   User ID.
     334                                         * @param string   $cap       Capability name.
     335                                         * @param string[] $caps      Array of the user's capabilities.
     336                                         */
     337                                        $allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps );
     338                                }
     339
     340                                if ( ! empty( $object_subtype ) ) {
     341
     342                                        /**
     343                                         * Filters whether the user is allowed to edit meta for specific object types/subtypes.
     344                                         *
     345                                         * Return true to have the mapped meta caps from `edit_{$object_type}` apply.
     346                                         *
     347                                         * The dynamic portion of the hook name, `$object_type` refers to the object type being filtered.
     348                                         * The dynamic portion of the hook name, `$object_subtype` refers to the object subtype being filtered.
     349                                         * The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap().
     350                                         *
     351                                         * @since 4.6.0 As `auth_post_{$post_type}_meta_{$meta_key}`.
     352                                         * @since 4.7.0
     353                                         * @deprecated 5.0.0 Use `auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}`
     354                                         *
     355                                         * @param bool     $allowed   Whether the user can add the object meta. Default false.
     356                                         * @param string   $meta_key  The meta key.
     357                                         * @param int      $object_id Object ID.
     358                                         * @param int      $user_id   User ID.
     359                                         * @param string   $cap       Capability name.
     360                                         * @param string[] $caps      Array of the user's capabilities.
     361                                         */
     362                                        $allowed = apply_filters_deprecated( "auth_{$object_type}_{$object_subtype}_meta_{$meta_key}", array( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ), '5.0.0', "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" );
     363                                }
     364
    375365                                if ( ! $allowed ) {
    376366                                        $caps[] = $cap;
    377367                                }
    378                         } elseif ( $meta_key && is_protected_meta( $meta_key, $object_type ) ) {
    379                                 $caps[] = $cap;
    380368                        }
    381369                        break;
    382370                case 'edit_comment':
  • src/wp-includes/meta.php

     
    4444                return false;
    4545        }
    4646
     47        $meta_subtype = get_object_subtype( $meta_type, $object_id );
     48
    4749        $column = sanitize_key( $meta_type . '_id' );
    4850
    4951        // expected_slashed ($meta_key)
    5052        $meta_key   = wp_unslash( $meta_key );
    5153        $meta_value = wp_unslash( $meta_value );
    52         $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
     54        $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
    5355
    5456        /**
    5557         * Filters whether to add metadata of a specific type.
     
    165167                return false;
    166168        }
    167169
     170        $meta_subtype = get_object_subtype( $meta_type, $object_id );
     171
    168172        $column    = sanitize_key( $meta_type . '_id' );
    169173        $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
    170174
     
    173177        $meta_key     = wp_unslash( $meta_key );
    174178        $passed_value = $meta_value;
    175179        $meta_value   = wp_unslash( $meta_value );
    176         $meta_value   = sanitize_meta( $meta_key, $meta_value, $meta_type );
     180        $meta_value   = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
    177181
    178182        /**
    179183         * Filters whether to update metadata of a specific type.
     
    666670                        return false;
    667671                }
    668672
     673                $meta_subtype = get_object_subtype( $meta_type, $object_id );
     674
    669675                // Sanitize the meta
    670676                $_meta_value = $meta_value;
    671                 $meta_value  = sanitize_meta( $meta_key, $meta_value, $meta_type );
     677                $meta_value  = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
    672678                $meta_value  = maybe_serialize( $meta_value );
    673679
    674680                // Format the data query arguments.
     
    968974 * Sanitize meta value.
    969975 *
    970976 * @since 3.1.3
     977 * @since 5.0.0 The `$object_subtype` parameter was added.
    971978 *
    972979 * @param string $meta_key       Meta key.
    973980 * @param mixed  $meta_value     Meta value to sanitize.
     
    975982 *
    976983 * @return mixed Sanitized $meta_value.
    977984 */
    978 function sanitize_meta( $meta_key, $meta_value, $object_type ) {
     985function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype = '' ) {
     986        if ( ! empty( $object_subtype ) && has_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) {
     987
     988                /**
     989                 * Filters the sanitization of a specific meta key of a specific meta type and subtype.
     990                 *
     991                 * The dynamic portions of the hook name, `$object_type`, `$meta_key`,
     992                 * and `$object_subtype`, refer to the metadata object type (comment, post, term or user),
     993                 * the meta key value, and the object subtype respectively.
     994                 *
     995                 * @since 5.0.0
     996                 *
     997                 * @param mixed  $meta_value     Meta value to sanitize.
     998                 * @param string $meta_key       Meta key.
     999                 * @param string $object_type    Object type.
     1000                 * @param string $object_subtype Object subtype.
     1001                 */
     1002                return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $meta_value, $meta_key, $object_type, $object_subtype );
     1003        }
     1004
    9791005        /**
    9801006         * Filters the sanitization of a specific meta key of a specific meta type.
    9811007         *
     
    9951021/**
    9961022 * Registers a meta key.
    9971023 *
     1024 * It is recommended to register meta keys for a specific combination of object type and object subtype. If passing
     1025 * an object subtype is omitted, the meta key will be registered for the entire object type, however it can be partly
     1026 * overridden in case a more specific meta key of the same name exists for the same object type and a subtype.
     1027 *
     1028 * If an object type does not support any subtypes, such as users or comments, you should commonly call this function
     1029 * without passing a subtype.
     1030 *
    9981031 * @since 3.3.0
    9991032 * @since 4.6.0 {@link https://core.trac.wordpress.org/ticket/35658 Modified
    10001033 *              to support an array of data to attach to registered meta keys}. Previous arguments for
    10011034 *              `$sanitize_callback` and `$auth_callback` have been folded into this array.
     1035 * @since 5.0.0 The `$object_subtype` argument was added to the arguments array.
    10021036 *
    10031037 * @param string $object_type    Type of object this meta is registered to.
    10041038 * @param string $meta_key       Meta key to register.
    10051039 * @param array  $args {
    10061040 *     Data used to describe the meta key when registered.
    1007  *
     1041 *     
     1042 *     @type string $object_subtype    A subtype; e.g. if the object type is "post", the post type. If left empty,
     1043 *                                     the meta key will be registered on the entire object type. Default empty.
    10081044 *     @type string $type              The type of data associated with this meta key.
    10091045 *                                     Valid values are 'string', 'boolean', 'integer', and 'number'.
    10101046 *     @type string $description       A description of the data attached to this meta key.
     
    10271063        }
    10281064
    10291065        $defaults = array(
     1066                'object_subtype'    => '',
    10301067                'type'              => 'string',
    10311068                'description'       => '',
    10321069                'single'            => false,
     
    10671104        $args = apply_filters( 'register_meta_args', $args, $defaults, $object_type, $meta_key );
    10681105        $args = wp_parse_args( $args, $defaults );
    10691106
     1107        $object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : '';
     1108
    10701109        // If `auth_callback` is not provided, fall back to `is_protected_meta()`.
    10711110        if ( empty( $args['auth_callback'] ) ) {
    10721111                if ( is_protected_meta( $meta_key, $object_type ) ) {
     
    10781117
    10791118        // Back-compat: old sanitize and auth callbacks are applied to all of an object type.
    10801119        if ( is_callable( $args['sanitize_callback'] ) ) {
    1081                 add_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'], 10, 3 );
     1120                if ( ! empty( $object_subtype ) ) {
     1121                        add_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'], 10, 4 );
     1122                } else {
     1123                        add_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'], 10, 3 );
     1124                }
    10821125        }
    10831126
    10841127        if ( is_callable( $args['auth_callback'] ) ) {
    1085                 add_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'], 10, 6 );
     1128                if ( ! empty( $object_subtype ) ) {
     1129                        add_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'], 10, 6 );
     1130                } else {
     1131                        add_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'], 10, 6 );
     1132                }
    10861133        }
    10871134
    10881135        // Global registry only contains meta keys registered with the array of arguments added in 4.6.0.
    10891136        if ( ! $has_old_auth_cb && ! $has_old_sanitize_cb ) {
    1090                 $wp_meta_keys[ $object_type ][ $meta_key ] = $args;
     1137                unset( $args['object_subtype'] );
    10911138
     1139                $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args;
     1140
    10921141                return true;
    10931142        }
    10941143
     
    10991148 * Checks if a meta key is registered.
    11001149 *
    11011150 * @since 4.6.0
     1151 * @since 5.0.0 The `$object_subtype` parameter was added.
    11021152 *
    11031153 * @param string $object_type    The type of object.
    11041154 * @param string $meta_key       The meta key.
     1155 * @param string $object_subtype Optional. The subtype of the object type.
    11051156 *
    1106  * @return bool True if the meta key is registered to the object type. False if not.
     1157 * @return bool True if the meta key is registered to the object type and, if provided,
     1158 *              the object subtype. False if not.
    11071159 */
    1108 function registered_meta_key_exists( $object_type, $meta_key ) {
    1109         global $wp_meta_keys;
     1160function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) {
     1161        $meta_keys = get_registered_meta_keys( $object_type, $object_subtype );
    11101162
    1111         if ( ! is_array( $wp_meta_keys ) ) {
    1112                 return false;
    1113         }
    1114 
    1115         if ( ! isset( $wp_meta_keys[ $object_type ] ) ) {
    1116                 return false;
    1117         }
    1118 
    1119         if ( isset( $wp_meta_keys[ $object_type ][ $meta_key ] ) ) {
    1120                 return true;
    1121         }
    1122 
    1123         return false;
     1163        return isset( $meta_keys[ $meta_key ] );
    11241164}
    11251165
    11261166/**
     
    11271167 * Unregisters a meta key from the list of registered keys.
    11281168 *
    11291169 * @since 4.6.0
     1170 * @since 5.0.0 The `$object_subtype` parameter was added.
    11301171 *
    1131  * @param string $object_type The type of object.
    1132  * @param string $meta_key    The meta key.
     1172 * @param string $object_type    The type of object.
     1173 * @param string $meta_key       The meta key.
     1174 * @param string $object_subtype Optional. The subtype of the object type.
    11331175 * @return bool True if successful. False if the meta key was not registered.
    11341176 */
    1135 function unregister_meta_key( $object_type, $meta_key ) {
     1177function unregister_meta_key( $object_type, $meta_key, $object_subtype = '' ) {
    11361178        global $wp_meta_keys;
    11371179
    1138         if ( ! registered_meta_key_exists( $object_type, $meta_key ) ) {
     1180        if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
    11391181                return false;
    11401182        }
    11411183
    1142         $args = $wp_meta_keys[ $object_type ][ $meta_key ];
     1184        $args = $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ];
    11431185
    11441186        if ( isset( $args['sanitize_callback'] ) && is_callable( $args['sanitize_callback'] ) ) {
    1145                 remove_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'] );
     1187                if ( ! empty( $object_subtype ) ) {
     1188                        remove_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'] );
     1189                } else {
     1190                        remove_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'] );
     1191                }
    11461192        }
    11471193
    11481194        if ( isset( $args['auth_callback'] ) && is_callable( $args['auth_callback'] ) ) {
    1149                 remove_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'] );
     1195                if ( ! empty( $object_subtype ) ) {
     1196                        remove_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'] );
     1197                } else {
     1198                        remove_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'] );
     1199                }
    11501200        }
    11511201
    1152         unset( $wp_meta_keys[ $object_type ][ $meta_key ] );
     1202        unset( $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] );
    11531203
    11541204        // Do some clean up
     1205        if ( empty( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) {
     1206                unset( $wp_meta_keys[ $object_type ][ $object_subtype ] );
     1207        }
    11551208        if ( empty( $wp_meta_keys[ $object_type ] ) ) {
    11561209                unset( $wp_meta_keys[ $object_type ] );
    11571210        }
     
    11631216 * Retrieves a list of registered meta keys for an object type.
    11641217 *
    11651218 * @since 4.6.0
     1219 * @since 5.0.0 The `$object_subtype` parameter was added.
    11661220 *
    1167  * @param string $object_type The type of object. Post, comment, user, term.
     1221 * @param string $object_type    The type of object. Post, comment, user, term.
     1222 * @param string $object_subtype Optional. The subtype of the object type.
    11681223 * @return array List of registered meta keys.
    11691224 */
    1170 function get_registered_meta_keys( $object_type ) {
     1225function get_registered_meta_keys( $object_type, $object_subtype = '' ) {
    11711226        global $wp_meta_keys;
    11721227
    1173         if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $object_type ] ) ) {
     1228        if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $object_type ] ) || ! isset( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) {
    11741229                return array();
    11751230        }
    11761231
    1177         return $wp_meta_keys[ $object_type ];
     1232        return $wp_meta_keys[ $object_type ][ $object_subtype ];
    11781233}
    11791234
    11801235/**
    11811236 * Retrieves registered metadata for a specified object.
    11821237 *
     1238 * The results include both meta that is registered specifically for the
     1239 * object's subtype and meta that is registered for the entire object type.
     1240 *
    11831241 * @since 4.6.0
    11841242 *
    11851243 * @param string $object_type Type of object to request metadata for. (e.g. comment, post, term, user)
     
    11871245 * @param string $meta_key    Optional. Registered metadata key. If not specified, retrieve all registered
    11881246 *                            metadata for the specified object.
    11891247 * @return mixed A single value or array of values for a key if specified. An array of all registered keys
    1190  *               and values for an object ID if not.
     1248 *               and values for an object ID if not. False if a given $meta_key is not registered.
    11911249 */
    11921250function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) {
     1251        $object_subtype = get_object_subtype( $object_type, $object_id );
     1252
    11931253        if ( ! empty( $meta_key ) ) {
    1194                 if ( ! registered_meta_key_exists( $object_type, $meta_key ) ) {
     1254                if ( ! empty( $object_subtype ) && ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
     1255                        $object_subtype = '';
     1256                }
     1257
     1258                if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
    11951259                        return false;
    11961260                }
    1197                 $meta_keys     = get_registered_meta_keys( $object_type );
     1261
     1262                $meta_keys     = get_registered_meta_keys( $object_type, $object_subtype );
    11981263                $meta_key_data = $meta_keys[ $meta_key ];
    11991264
    12001265                $data = get_metadata( $object_type, $object_id, $meta_key, $meta_key_data['single'] );
     
    12031268        }
    12041269
    12051270        $data = get_metadata( $object_type, $object_id );
     1271        if ( ! $data ) {
     1272                return array();
     1273        }
    12061274
    1207         $meta_keys       = get_registered_meta_keys( $object_type );
    1208         $registered_data = array();
    1209 
    1210         // Someday, array_filter()
    1211         foreach ( $meta_keys as $k => $v ) {
    1212                 if ( isset( $data[ $k ] ) ) {
    1213                         $registered_data[ $k ] = $data[ $k ];
    1214                 }
     1275        $meta_keys = get_registered_meta_keys( $object_type );
     1276        if ( ! empty( $object_subtype ) ) {
     1277                $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $object_type, $object_subtype ) );
    12151278        }
    12161279
    1217         return $registered_data;
     1280        return array_intersect_key( $data, $meta_keys );
    12181281}
    12191282
    12201283/**
     
    12231286 * to be explicitly turned off is a warranty seal of sorts.
    12241287 *
    12251288 * @access private
    1226  * @since  4.6.0
     1289 * @since 4.6.0
    12271290 *
    1228  * @param  array $args         Arguments from `register_meta()`.
    1229  * @param  array $default_args Default arguments for `register_meta()`.
     1291 * @param array $args         Arguments from `register_meta()`.
     1292 * @param array $default_args Default arguments for `register_meta()`.
    12301293 *
    12311294 * @return array Filtered arguments.
    12321295 */
    12331296function _wp_register_meta_args_whitelist( $args, $default_args ) {
    1234         $whitelist = array_keys( $default_args );
     1297        return array_intersect_key( $args, $default_args );
     1298}
    12351299
    1236         // In an anonymous function world, this would be better as an array_filter()
    1237         foreach ( $args as $key => $value ) {
    1238                 if ( ! in_array( $key, $whitelist ) ) {
    1239                         unset( $args[ $key ] );
    1240                 }
     1300/**
     1301 * Returns the object subtype for a given object ID of a specific type.
     1302 *
     1303 * @since 5.0.0
     1304 *
     1305 * @param string $object_type Type of object to request metadata for. (e.g. comment, post, term, user)
     1306 * @param int    $object_id   ID of the object to retrieve its subtype.
     1307 * @return string The object subtype or an empty string if unspecified subtype.
     1308 */
     1309function get_object_subtype( $object_type, $object_id ) {
     1310        $object_id      = (int) $object_id;
     1311        $object_subtype = '';
     1312
     1313        switch ( $object_type ) {
     1314                case 'post':
     1315                        $post = get_post( $object_id );
     1316                        if ( ! $post ) {
     1317                                break;
     1318                        }
     1319
     1320                        $object_subtype = get_post_type( $post );
     1321                        break;
     1322
     1323                case 'comment':
     1324                        $comment = get_comment( $object_id );
     1325                        if ( ! $comment ) {
     1326                                break;
     1327                        }
     1328
     1329                        $object_subtype = empty( $comment->comment_type ) ? 'comment' : $comment->comment_type;;
     1330                        break;
     1331
     1332                case 'term':
     1333                        $term = get_term( $object_id );
     1334                        if ( ! $term instanceof WP_Term ) {
     1335                                break;
     1336                        }
     1337
     1338                        $object_subtype = $term->taxonomy;
     1339                        break;
     1340
     1341                case 'user':
     1342                        $user = get_user_by( 'id', $object_id );
     1343                        if ( ! $user ) {
     1344                                break;
     1345                        }
     1346
     1347                        $object_subtype = 'user';
     1348                        break;
     1349                default:
     1350
     1351                        /**
     1352                         * Filters the object subtype identifier for a non standard object type.
     1353                         *
     1354                         * The dynamic portion of the hook, `$object_type`, refers to the object
     1355                         * type (post, comment, term, or user).
     1356                         *
     1357                         * @since 5.0.0
     1358                         *
     1359                         * @param string $object_subtype Empty string to override.
     1360                         * @param int    $object_id      ID of the object to get the subtype for.
     1361                         */
     1362                        $object_subtype = apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id );
    12411363        }
    12421364
    1243         return $args;
     1365        return $object_subtype;
    12441366}
  • src/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php

     
    2828        }
    2929
    3030        /**
     31         * Retrieves the object meta subtype.
     32         *
     33         * @since 5.0.0
     34         *
     35         * @return string 'comment' There are no subtypes.
     36         */
     37        protected function get_meta_subtype() {
     38                return 'comment';
     39        }
     40
     41        /**
    3142         * Retrieves the type for register_rest_field() in the context of comments.
    3243         *
    3344         * @since 4.7.0
  • src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

     
    2525        abstract protected function get_meta_type();
    2626
    2727        /**
     28         * Retrieves the object meta subtype.
     29         *
     30         * @since 5.0.0
     31         *
     32         * @return string Subtype for the meta type, or empty string if no specific subtype.
     33         */
     34        protected function get_meta_subtype() {
     35                return '';
     36        }
     37
     38        /**
    2839         * Retrieves the object type for register_rest_field().
    2940         *
    3041         * @since 4.7.0
     
    340351        protected function get_registered_fields() {
    341352                $registered = array();
    342353
    343                 foreach ( get_registered_meta_keys( $this->get_meta_type() ) as $name => $args ) {
     354                $meta_type    = $this->get_meta_type();
     355                $meta_subtype = $this->get_meta_subtype();
     356
     357                $meta_keys = get_registered_meta_keys( $meta_type );
     358                if ( ! empty( $meta_subtype ) ) {
     359                        $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $meta_type, $meta_subtype ) );
     360                }
     361
     362                foreach ( $meta_keys as $name => $args ) {
    344363                        if ( empty( $args['show_in_rest'] ) ) {
    345364                                continue;
    346365                        }
  • src/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php

     
    4747        }
    4848
    4949        /**
     50         * Retrieves the object meta subtype.
     51         *
     52         * @since 5.0.0
     53         *
     54         * @return string Subtype for the meta type, or empty string if no specific subtype.
     55         */
     56        protected function get_meta_subtype() {
     57                return $this->post_type;
     58        }
     59
     60        /**
    5061         * Retrieves the type for register_rest_field().
    5162         *
    5263         * @since 4.7.0
  • src/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php

     
    4747        }
    4848
    4949        /**
     50         * Retrieves the object meta subtype.
     51         *
     52         * @since 5.0.0
     53         *
     54         * @return string Subtype for the meta type, or empty string if no specific subtype.
     55         */
     56        protected function get_meta_subtype() {
     57                return $this->taxonomy;
     58        }
     59
     60        /**
    5061         * Retrieves the type for register_rest_field().
    5162         *
    5263         * @since 4.7.0
  • src/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php

     
    2828        }
    2929
    3030        /**
     31         * Retrieves the object meta subtype.
     32         *
     33         * @since 5.0.0
     34         *
     35         * @return string 'user' There are no subtypes.
     36         */
     37        protected function get_meta_subtype() {
     38                return 'user';
     39        }
     40
     41        /**
    3142         * Retrieves the type for register_rest_field().
    3243         *
    3344         * @since 4.7.0
  • tests/phpunit/tests/meta/registerMeta.php

     
    7474
    7575                $expected = array(
    7676                        'post' => array(
    77                                 'flight_number' => array(
    78                                         'type'              => 'string',
    79                                         'description'       => '',
    80                                         'single'            => false,
    81                                         'sanitize_callback' => null,
    82                                         'auth_callback'     => '__return_true',
    83                                         'show_in_rest'      => false,
     77                                '' => array(
     78                                        'flight_number' => array(
     79                                                'type'              => 'string',
     80                                                'description'       => '',
     81                                                'single'            => false,
     82                                                'sanitize_callback' => null,
     83                                                'auth_callback'     => '__return_true',
     84                                                'show_in_rest'      => false,
     85                                        ),
    8486                                ),
    8587                        ),
    8688                );
     
    8890                $this->assertEquals( $actual, $expected );
    8991        }
    9092
    91         public function test_register_meta_with_term_object_type_populates_wp_meta_keys() {
     93        public function test_register_meta_with_post_subtype_populates_wp_meta_keys() {
    9294                global $wp_meta_keys;
    93                 register_meta( 'term', 'category_icon', array() );
     95                register_meta( 'post', 'flight_number', array( 'object_subtype' => 'journey' ) );
    9496                $actual = $wp_meta_keys;
    95                 unregister_meta_key( 'term', 'category_icon' );
     97                unregister_meta_key( 'post', 'flight_number', 'journey' );
    9698
    9799                $expected = array(
    98                         'term' => array(
    99                                 'category_icon' => array(
    100                                         'type'              => 'string',
    101                                         'description'       => '',
    102                                         'single'            => false,
    103                                         'sanitize_callback' => null,
    104                                         'auth_callback'     => '__return_true',
    105                                         'show_in_rest'      => false,
     100                        'post' => array(
     101                                'journey' => array(
     102                                        'flight_number' => array(
     103                                                'type'              => 'string',
     104                                                'description'       => '',
     105                                                'single'            => false,
     106                                                'sanitize_callback' => null,
     107                                                'auth_callback'     => '__return_true',
     108                                                'show_in_rest'      => false,
     109                                        ),
    106110                                ),
    107111                        ),
    108112                );
     
    110114                $this->assertEquals( $actual, $expected );
    111115        }
    112116
     117        /**
     118         * @depends test_register_meta_with_post_subtype_populates_wp_meta_keys
     119         */
     120        public function test_unregister_meta_with_post_subtype() {
     121                global $wp_meta_keys;
     122                register_meta( 'post', 'flight_number', array( 'object_subtype' => 'journey' ) );
     123                $registered = $wp_meta_keys;
     124                unregister_meta_key( 'post', 'flight_number', 'journey' );
     125                $unregistered = $wp_meta_keys;
     126
     127                $expected_registered = array(
     128                        'post' => array(
     129                                'journey' => array(
     130                                        'flight_number' => array(
     131                                                'type'              => 'string',
     132                                                'description'       => '',
     133                                                'single'            => false,
     134                                                'sanitize_callback' => null,
     135                                                'auth_callback'     => '__return_true',
     136                                                'show_in_rest'      => false,
     137                                        ),
     138                                ),
     139                        ),
     140                );
     141
     142                $this->assertEquals( $registered, $expected_registered );
     143
     144                $this->assertEmpty( $unregistered );
     145        }
     146
     147        /**
     148         * @depends test_register_meta_with_post_subtype_populates_wp_meta_keys
     149         */
     150        public function test_meta_with_post_subtype_unregistering_type_keeps_meta_key() {
     151                global $wp_meta_keys;
     152                register_meta( 'post', 'flight_number', array( 'object_subtype' => 'journey' ) );
     153                $registered = $wp_meta_keys;
     154                unregister_meta_key( 'post', 'flight_number' ); // unregister meta key without subtype
     155                $unregistered = $wp_meta_keys;
     156                unregister_meta_key( 'post', 'flight_number', 'journey' );
     157
     158                $expected = array(
     159                        'post' => array(
     160                                'journey' => array(
     161                                        'flight_number' => array(
     162                                                'type'              => 'string',
     163                                                'description'       => '',
     164                                                'single'            => false,
     165                                                'sanitize_callback' => null,
     166                                                'auth_callback'     => '__return_true',
     167                                                'show_in_rest'      => false,
     168                                        ),
     169                                ),
     170                        ),
     171                );
     172
     173                $this->assertEquals( $registered, $expected );
     174                $this->assertEquals( $unregistered, $expected  );
     175        }
     176
    113177        public function test_register_meta_with_deprecated_sanitize_callback_does_not_populate_wp_meta_keys() {
    114178                global $wp_meta_keys;
    115179
     
    148212
    149213                $expected = array(
    150214                        'post' => array(
    151                                 'flight_number' => array(
    152                                         'type'              => 'string',
    153                                         'description'       => '',
    154                                         'single'            => false,
    155                                         'sanitize_callback' => array( $this, '_new_sanitize_meta_cb' ),
    156                                         'auth_callback'     => '__return_true',
    157                                         'show_in_rest'      => false,
     215                                '' => array(
     216                                        'flight_number' => array(
     217                                                'type'              => 'string',
     218                                                'description'       => '',
     219                                                'single'            => false,
     220                                                'sanitize_callback' => array( $this, '_new_sanitize_meta_cb' ),
     221                                                'auth_callback'     => '__return_true',
     222                                                'show_in_rest'      => false,
     223                                        ),
    158224                                ),
    159225                        ),
    160226                );
  • tests/phpunit/tests/rest-api/rest-post-meta-fields.php

     
    1212class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
    1313        protected static $wp_meta_keys_saved;
    1414        protected static $post_id;
     15        protected static $custom_post_type_id;
    1516
    1617        public static function wpSetUpBeforeClass( $factory ) {
    1718                self::$wp_meta_keys_saved = $GLOBALS['wp_meta_keys'];
    1819                self::$post_id            = $factory->post->create();
     20
     21                self::$custom_post_type_id = $factory->post->create( array( 'post_type' => 'cpt' ) );
    1922        }
    2023
    2124        public static function wpTearDownAfterClass() {
    2225                $GLOBALS['wp_meta_keys'] = self::$wp_meta_keys_saved;
    2326                wp_delete_post( self::$post_id, true );
     27                wp_delete_post( self::$custom_post_type_id, true );
    2428        }
    2529
    2630        public function setUp() {
     
    120124                        )
    121125                );
    122126
     127                $args = array(
     128                        'show_in_rest' => true,
     129                        'supports' => array( 'custom-fields' ),
     130                );
     131                register_post_type( 'cpt', $args );
     132
     133                register_meta( 'post', 'test_cpt_single', array(
     134                        'show_in_rest' => true,
     135                        'single' => true,
     136                        'object_subtype' => 'cpt',
     137                ) );
     138
     139                register_meta( 'post', 'test_cpt_bad_auth', array(
     140                        'show_in_rest' => true,
     141                        'single' => true,
     142                        'auth_callback' => '__return_false',
     143                        'object_subtype' => 'cpt',
     144                ) );
     145
     146                // registering the key 'test_single' on the cpt post type
     147                register_meta( 'post', 'test_single', array(
     148                        'show_in_rest' => true,
     149                        'single' => true,
     150                        'auth_callback' => '__return_false',
     151                        'object_subtype' => 'cpt',
     152                ) );
     153
    123154                /** @var WP_REST_Server $wp_rest_server */
    124155                global $wp_rest_server;
    125156                $wp_rest_server = new Spy_REST_Server;
     
    10471078                $this->assertArrayNotHasKey( 'test_no_type', $meta_schema );
    10481079        }
    10491080
     1081        public function test_register_custom_post_meta_get_value () {
     1082                add_post_meta( self::$custom_post_type_id, 'test_cpt_single', 'testcptvalue' );
     1083
     1084                $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/cpt/%d', self::$custom_post_type_id ) );
     1085                $response = rest_get_server()->dispatch( $request );
     1086
     1087                $this->assertEquals( 200, $response->get_status() );
     1088
     1089                $data = $response->get_data();
     1090                $this->assertArrayHasKey( 'meta', $data );
     1091
     1092                $meta = (array) $data['meta'];
     1093                $this->assertArrayHasKey( 'test_cpt_single', $meta );
     1094                $this->assertEquals( 'testcptvalue', $meta['test_cpt_single'] );
     1095        }
     1096
     1097        public function test_register_custom_post_meta_not_in_post () {
     1098                add_post_meta( self::$post_id, 'test_cpt_single', 'testcptvalue' );
     1099
     1100                $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1101                $response = rest_get_server()->dispatch( $request );
     1102
     1103                $this->assertEquals( 200, $response->get_status() );
     1104
     1105                $data = $response->get_data();
     1106                $this->assertArrayHasKey( 'meta', $data );
     1107
     1108                $meta = (array) $data['meta'];
     1109                $this->assertArrayNotHasKey( 'test_cpt_single', $meta );
     1110        }
     1111
     1112        public function test_set_value_unregistered_custom_post_type_registered_object() {
     1113                // Ensure no data exists currently.
     1114                $values = get_post_meta( self::$custom_post_type_id, 'test_multi', false );
     1115                $this->assertEmpty( $values );
     1116
     1117                $this->grant_write_permission();
     1118
     1119                $data    = array(
     1120                        'meta' => array(
     1121                                'test_multi' => 'test_value',
     1122                        ),
     1123                );
     1124                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/cpt/%d', self::$custom_post_type_id ) );
     1125                $request->set_body_params( $data );
     1126
     1127                $response = rest_get_server()->dispatch( $request );
     1128                $this->assertEquals( 200, $response->get_status() );
     1129
     1130                $meta = get_post_meta( self::$custom_post_type_id, 'test_multi', false );
     1131                $this->assertNotEmpty( $meta );
     1132                $this->assertCount( 1, $meta );
     1133                $this->assertEquals( 'test_value', $meta[0] );
     1134
     1135                $data = $response->get_data();
     1136                $meta = (array) $data['meta'];
     1137                $this->assertArrayHasKey( 'test_multi', $meta );
     1138                $this->assertEquals( 'test_value', $meta['test_multi'][0] );
     1139        }
     1140
     1141        public function test_set_value_custom_post_type() {
     1142                // Ensure no data exists currently.
     1143                $values = get_post_meta( self::$custom_post_type_id, 'test_cpt_single', false );
     1144                $this->assertEmpty( $values );
     1145
     1146                $this->grant_write_permission();
     1147
     1148                $data    = array(
     1149                        'meta' => array(
     1150                                'test_cpt_single' => 'test_value',
     1151                        ),
     1152                );
     1153                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/cpt/%d', self::$custom_post_type_id ) );
     1154                $request->set_body_params( $data );
     1155
     1156                $response = rest_get_server()->dispatch( $request );
     1157                $this->assertEquals( 200, $response->get_status() );
     1158
     1159                $meta = get_post_meta( self::$custom_post_type_id, 'test_cpt_single', false );
     1160                $this->assertNotEmpty( $meta );
     1161                $this->assertCount( 1, $meta );
     1162                $this->assertEquals( 'test_value', $meta[0] );
     1163
     1164                $data = $response->get_data();
     1165                $meta = (array) $data['meta'];
     1166                $this->assertArrayHasKey( 'test_cpt_single', $meta );
     1167                $this->assertEquals( 'test_value', $meta['test_cpt_single'] );
     1168        }
     1169
    10501170        /**
     1171         * @depends test_set_value_custom_post_type
     1172         */
     1173        public function test_set_value_custom_post_type_blocked() {
     1174                $data = array(
     1175                        'meta' => array(
     1176                                'test_cpt_bad_auth' => 'test_value',
     1177                        ),
     1178                );
     1179
     1180                $this->grant_write_permission();
     1181
     1182                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/cpt/%d', self::$custom_post_type_id ) );
     1183                $request->set_body_params( $data );
     1184
     1185                $response = rest_get_server()->dispatch( $request );
     1186                $this->assertErrorResponse( 'rest_cannot_update', $response, 403 );
     1187                $this->assertEmpty( get_post_meta( self::$custom_post_type_id, 'test_cpt_bad_auth', false ) );
     1188        }
     1189
     1190        /**
     1191         * @depends test_set_value_custom_post_type_blocked
     1192         */
     1193        public function test_set_value_custom_post_type_duplicate_key_blocked() {
     1194                $data = array(
     1195                        'meta' => array(
     1196                                'test_single' => 'test_value',
     1197                        ),
     1198                );
     1199
     1200                $this->grant_write_permission();
     1201
     1202                $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     1203                $request->set_body_params( $data );
     1204
     1205                $response = rest_get_server()->dispatch( $request );
     1206                $this->assertEquals( get_post_meta( self::$post_id, 'test_single', true ), 'test_value' );
     1207
     1208                $request_cpt = new WP_REST_Request( 'POST', sprintf( '/wp/v2/cpt/%d', self::$custom_post_type_id ) );
     1209                $request_cpt->set_body_params( $data );
     1210
     1211                $response_cpt = rest_get_server()->dispatch( $request_cpt );
     1212
     1213                $this->assertErrorResponse( 'rest_cannot_update', $response_cpt, 403 );
     1214                $this->assertEmpty( get_post_meta( self::$custom_post_type_id, 'test_single', true ) );
     1215        }
     1216
     1217        /**
    10511218         * Internal function used to disable an insert query which
    10521219         * will trigger a wpdb error for testing purposes.
    10531220         */