Make WordPress Core

Changeset 43378


Ignore:
Timestamp:
06/21/2018 09:06:50 PM (6 years ago)
Author:
kadamwhite
Message:

REST API: Support meta registration for specific object subtypes.

Introduce an object_subtype argument to the args array for register_meta() which can be used to limit meta registration to a single subtype (e.g. a custom post type or taxonomy, vs all posts or taxonomies).

Introduce register_post_meta() and register_term_meta() wrapper methods for register_meta to provide a convenient interface for the common case of registering meta for a specific taxonomy or post type. These methods work the way plugin developers have often expected register_meta to function, and should be used in place of direct register_meta where possible.

Props flixos90, tharsheblows, spacedmonkey.
Fixes #38323.

Location:
trunk
Files:
14 edited

Legend:

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

    r43286 r43378  
    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                     }
    290 
    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 ) ) {
     284            $object_subtype = get_object_subtype( $object_type, $object_id );
     285
     286            if ( empty( $object_subtype ) ) {
    323287                $caps[] = 'do_not_allow';
    324288                break;
     
    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 ) {
    333 
    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 );
    353 
    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 );
     295            if ( $meta_key ) {
     296                $allowed = ! is_protected_meta( $meta_key, $object_type );
     297
     298                if ( ! empty( $object_subtype ) && has_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) {
     299
     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 {
     318
     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                }
    374364
    375365                if ( ! $allowed ) {
    376366                    $caps[] = $cap;
    377367                }
    378             } elseif ( $meta_key && is_protected_meta( $meta_key, $object_type ) ) {
    379                 $caps[] = $cap;
    380368            }
    381369            break;
  • trunk/src/wp-includes/meta.php

    r43043 r43378  
    4545    }
    4646
     47    $meta_subtype = get_object_subtype( $meta_type, $object_id );
     48
    4749    $column = sanitize_key( $meta_type . '_id' );
    4850
     
    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    /**
     
    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';
     
    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    /**
     
    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
     
    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.
     
    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.
     
    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.
     
    10061040 *     Data used to describe the meta key when registered.
    10071041 *
     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'.
     
    10281064
    10291065    $defaults = array(
     1066        'object_subtype'    => '',
    10301067        'type'              => 'string',
    10311068        'description'       => '',
     
    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'] ) ) {
     
    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'] );
     1138
     1139        $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args;
    10911140
    10921141        return true;
     
    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.
    1105  *
    1106  * @return bool True if the meta key is registered to the object type. False if not.
    1107  */
    1108 function registered_meta_key_exists( $object_type, $meta_key ) {
     1155 * @param string $object_subtype Optional. The subtype of the object type.
     1156 *
     1157 * @return bool True if the meta key is registered to the object type and, if provided,
     1158 *              the object subtype. False if not.
     1159 */
     1160function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) {
     1161    $meta_keys = get_registered_meta_keys( $object_type, $object_subtype );
     1162
     1163    return isset( $meta_keys[ $meta_key ] );
     1164}
     1165
     1166/**
     1167 * Unregisters a meta key from the list of registered keys.
     1168 *
     1169 * @since 4.6.0
     1170 * @since 5.0.0 The `$object_subtype` parameter was added.
     1171 *
     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.
     1175 * @return bool True if successful. False if the meta key was not registered.
     1176 */
     1177function unregister_meta_key( $object_type, $meta_key, $object_subtype = '' ) {
    11091178    global $wp_meta_keys;
    11101179
    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;
    1124 }
    1125 
    1126 /**
    1127  * Unregisters a meta key from the list of registered keys.
    1128  *
    1129  * @since 4.6.0
    1130  *
    1131  * @param string $object_type The type of object.
    1132  * @param string $meta_key    The meta key.
    1133  * @return bool True if successful. False if the meta key was not registered.
    1134  */
    1135 function unregister_meta_key( $object_type, $meta_key ) {
    1136     global $wp_meta_keys;
    1137 
    1138     if ( ! registered_meta_key_exists( $object_type, $meta_key ) ) {
    1139         return false;
    1140     }
    1141 
    1142     $args = $wp_meta_keys[ $object_type ][ $meta_key ];
     1180    if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
     1181        return false;
     1182    }
     1183
     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'] );
    1150     }
    1151 
    1152     unset( $wp_meta_keys[ $object_type ][ $meta_key ] );
     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        }
     1200    }
     1201
     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 ] );
     
    11641217 *
    11651218 * @since 4.6.0
    1166  *
    1167  * @param string $object_type The type of object. Post, comment, user, term.
     1219 * @since 5.0.0 The `$object_subtype` parameter was added.
     1220 *
     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.
     1237 *
     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.
    11821240 *
    11831241 * @since 4.6.0
     
    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
     
    12041269
    12051270    $data = get_metadata( $object_type, $object_id );
    1206 
    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         }
    1215     }
    1216 
    1217     return $registered_data;
     1271    if ( ! $data ) {
     1272        return array();
     1273    }
     1274
     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 ) );
     1278    }
     1279
     1280    return array_intersect_key( $data, $meta_keys );
    12181281}
    12191282
     
    12241287 *
    12251288 * @access private
    1226  * @since  4.6.0
    1227  *
    1228  * @param  array $args         Arguments from `register_meta()`.
    1229  * @param  array $default_args Default arguments for `register_meta()`.
     1289 * @since 4.6.0
     1290 *
     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 );
    1235 
    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         }
    1241     }
    1242 
    1243     return $args;
    1244 }
     1297    return array_intersect_key( $args, $default_args );
     1298}
     1299
     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_type = get_post_type( $object_id );
     1316
     1317            if ( ! empty( $post_type ) ) {
     1318                $object_subtype = $post_type;
     1319            }
     1320            break;
     1321
     1322        case 'term':
     1323            $term = get_term( $object_id );
     1324            if ( ! $term instanceof WP_Term ) {
     1325                break;
     1326            }
     1327
     1328            $object_subtype = $term->taxonomy;
     1329            break;
     1330
     1331        case 'comment':
     1332            $comment = get_comment( $object_id );
     1333            if ( ! $comment ) {
     1334                break;
     1335            }
     1336
     1337            $object_subtype = 'comment';
     1338            break;
     1339
     1340        case 'user':
     1341            $user = get_user_by( 'id', $object_id );
     1342            if ( ! $user ) {
     1343                break;
     1344            }
     1345
     1346            $object_subtype = 'user';
     1347            break;
     1348    }
     1349
     1350    /**
     1351     * Filters the object subtype identifier for a non standard object type.
     1352     *
     1353     * The dynamic portion of the hook, `$object_type`, refers to the object
     1354     * type (post, comment, term, or user).
     1355     *
     1356     * @since 5.0.0
     1357     *
     1358     * @param string $object_subtype Empty string to override.
     1359     * @param int    $object_id      ID of the object to get the subtype for.
     1360     */
     1361    return apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id );
     1362}
  • trunk/src/wp-includes/post.php

    r43011 r43378  
    19911991
    19921992/**
     1993 * Registers a meta key for posts.
     1994 *
     1995 * @since 5.0.0
     1996 *
     1997 * @param string $post_type Post type to register a meta key for. Pass an empty string
     1998 *                          to register the meta key across all existing post types.
     1999 * @param string $meta_key  The meta key to register.
     2000 * @param array  $args      Data used to describe the meta key when registered. See
     2001 *                          {@see register_meta()} for a list of supported arguments.
     2002 * @return bool True if the meta key was successfully registered, false if not.
     2003 */
     2004function register_post_meta( $post_type, $meta_key, array $args ) {
     2005    $args['object_subtype'] = $post_type;
     2006
     2007    return register_meta( 'post', $meta_key, $args );
     2008}
     2009
     2010/**
     2011 * Unregisters a meta key for posts.
     2012 *
     2013 * @since 5.0.0
     2014 *
     2015 * @param string $post_type Post type the meta key is currently registered for. Pass
     2016 *                          an empty string if the meta key is registered across all
     2017 *                          existing post types.
     2018 * @param string $meta_key  The meta key to unregister.
     2019 * @return bool True on success, false if the meta key was not previously registered.
     2020 */
     2021function unregister_post_meta( $post_type, $meta_key ) {
     2022    return unregister_meta_key( 'post', $meta_key, $post_type );
     2023}
     2024
     2025/**
    19932026 * Retrieve post meta fields, based on post ID.
    19942027 *
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php

    r41162 r43378  
    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     *
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

    r42343 r43378  
    2424     */
    2525    abstract protected function get_meta_type();
     26
     27    /**
     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    }
    2637
    2738    /**
     
    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;
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php

    r41162 r43378  
    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     *
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php

    r41162 r43378  
    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     *
  • trunk/src/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php

    r41162 r43378  
    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     *
  • trunk/src/wp-includes/taxonomy.php

    r43177 r43378  
    13241324
    13251325/**
     1326 * Registers a meta key for terms.
     1327 *
     1328 * @since 5.0.0
     1329 *
     1330 * @param string $taxonomy Taxonomy to register a meta key for. Pass an empty string
     1331 *                         to register the meta key across all existing taxonomies.
     1332 * @param string $meta_key The meta key to register.
     1333 * @param array  $args     Data used to describe the meta key when registered. See
     1334 *                         {@see register_meta()} for a list of supported arguments.
     1335 * @return bool True if the meta key was successfully registered, false if not.
     1336 */
     1337function register_term_meta( $taxonomy, $meta_key, array $args ) {
     1338    $args['object_subtype'] = $taxonomy;
     1339
     1340    return register_meta( 'term', $meta_key, $args );
     1341}
     1342
     1343/**
     1344 * Unregisters a meta key for terms.
     1345 *
     1346 * @since 5.0.0
     1347 *
     1348 * @param string $taxonomy Taxonomy the meta key is currently registered for. Pass
     1349 *                         an empty string if the meta key is registered across all
     1350 *                         existing taxonomies.
     1351 * @param string $meta_key The meta key to unregister.
     1352 * @return bool True on success, false if the meta key was not previously registered.
     1353 */
     1354function unregister_term_meta( $taxonomy, $meta_key ) {
     1355    return unregister_meta_key( 'term', $meta_key, $taxonomy );
     1356}
     1357
     1358/**
    13261359 * Determines whether a term exists.
    13271360 *
  • trunk/tests/phpunit/tests/meta/registerMeta.php

    r42343 r43378  
    44 */
    55class Tests_Meta_Register_Meta extends WP_UnitTestCase {
     6
    67    protected static $post_id;
     8    protected static $term_id;
     9    protected static $comment_id;
     10    protected static $user_id;
    711
    812    public static function wpSetUpBeforeClass( $factory ) {
    9         self::$post_id = $factory->post->create();
     13        self::$post_id    = $factory->post->create( array( 'post_type' => 'page' ) );
     14        self::$term_id    = $factory->term->create( array( 'taxonomy' => 'category' ) );
     15        self::$comment_id = $factory->comment->create();
     16        self::$user_id    = $factory->user->create();
     17    }
     18
     19    public static function wpTearDownAfterClass() {
     20        wp_delete_post( self::$post_id, true );
     21        wp_delete_term( self::$term_id, 'category' );
     22        wp_delete_comment( self::$comment_id, true );
     23        self::delete_user( self::$user_id );
    1024    }
    1125
     
    7589        $expected = array(
    7690            '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,
     91                '' => array(
     92                    'flight_number' => array(
     93                        'type'              => 'string',
     94                        'description'       => '',
     95                        'single'            => false,
     96                        'sanitize_callback' => null,
     97                        'auth_callback'     => '__return_true',
     98                        'show_in_rest'      => false,
     99                    ),
    84100                ),
    85101            ),
    86102        );
    87103
    88         $this->assertEquals( $actual, $expected );
     104        $this->assertEquals( $expected, $actual );
    89105    }
    90106
     
    97113        $expected = array(
    98114            '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,
     115                '' => array(
     116                    'category_icon' => array(
     117                        'type'              => 'string',
     118                        'description'       => '',
     119                        'single'            => false,
     120                        'sanitize_callback' => null,
     121                        'auth_callback'     => '__return_true',
     122                        'show_in_rest'      => false,
     123                    ),
    106124                ),
    107125            ),
    108126        );
    109127
    110         $this->assertEquals( $actual, $expected );
     128        $this->assertEquals( $expected, $actual );
    111129    }
    112130
     
    149167        $expected = array(
    150168            '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,
     169                '' => array(
     170                    'flight_number' => array(
     171                        'type'              => 'string',
     172                        'description'       => '',
     173                        'single'            => false,
     174                        'sanitize_callback' => array( $this, '_new_sanitize_meta_cb' ),
     175                        'auth_callback'     => '__return_true',
     176                        'show_in_rest'      => false,
     177                    ),
    158178                ),
    159179            ),
     
    302322        $this->assertEmpty( $meta );
    303323    }
     324
     325    /**
     326     * @ticket 38323
     327     * @dataProvider data_get_types_and_subtypes
     328     */
     329    public function test_register_meta_with_subtype_populates_wp_meta_keys( $type, $subtype ) {
     330        global $wp_meta_keys;
     331
     332        register_meta( $type, 'flight_number', array( 'object_subtype' => $subtype ) );
     333
     334        $expected = array(
     335            $type => array(
     336                $subtype => array(
     337                    'flight_number' => array(
     338                        'type'              => 'string',
     339                        'description'       => '',
     340                        'single'            => false,
     341                        'sanitize_callback' => null,
     342                        'auth_callback'     => '__return_true',
     343                        'show_in_rest'      => false,
     344                    ),
     345                ),
     346            ),
     347        );
     348
     349        $actual = $wp_meta_keys;
     350
     351        // Reset global so subsequent data tests do not get polluted.
     352        $wp_meta_keys = array();
     353
     354        $this->assertEquals( $expected, $actual );
     355    }
     356
     357    /**
     358     * @ticket 38323
     359     * @dataProvider data_get_types_and_subtypes
     360     */
     361    public function test_unregister_meta_with_subtype_unpopulates_wp_meta_keys( $type, $subtype ) {
     362        global $wp_meta_keys;
     363
     364        register_meta( $type, 'flight_number', array( 'object_subtype' => $subtype ) );
     365        unregister_meta_key( $type, 'flight_number', $subtype );
     366
     367        $actual = $wp_meta_keys;
     368
     369        // Reset global so subsequent data tests do not get polluted.
     370        $wp_meta_keys = array();
     371
     372        $this->assertEmpty( $actual );
     373    }
     374
     375    /**
     376     * @ticket 38323
     377     * @dataProvider data_get_types_and_subtypes
     378     */
     379    public function test_unregister_meta_without_subtype_keeps_subtype_meta_key( $type, $subtype ) {
     380        global $wp_meta_keys;
     381
     382        register_meta( $type, 'flight_number', array( 'object_subtype' => $subtype ) );
     383
     384        // Unregister meta key without subtype.
     385        unregister_meta_key( $type, 'flight_number' );
     386
     387        $expected = array(
     388            $type => array(
     389                $subtype => array(
     390                    'flight_number' => array(
     391                        'type'              => 'string',
     392                        'description'       => '',
     393                        'single'            => false,
     394                        'sanitize_callback' => null,
     395                        'auth_callback'     => '__return_true',
     396                        'show_in_rest'      => false,
     397                    ),
     398                ),
     399            ),
     400        );
     401
     402        $actual = $wp_meta_keys;
     403
     404        // Reset global so subsequent data tests do not get polluted.
     405        $wp_meta_keys = array();
     406
     407        $this->assertEquals( $expected, $actual );
     408    }
     409
     410    /**
     411     * @ticket 38323
     412     * @dataProvider data_get_types_and_subtypes
     413     */
     414    public function test_get_registered_meta_keys_with_subtype( $type, $subtype ) {
     415        register_meta( $type, 'registered_key1', array( 'object_subtype' => $subtype ) );
     416        register_meta( $type, 'registered_key2', array( 'object_subtype' => $subtype ) );
     417
     418        $meta_keys = get_registered_meta_keys( $type, $subtype );
     419
     420        $this->assertArrayHasKey( 'registered_key1', $meta_keys );
     421        $this->assertArrayHasKey( 'registered_key2', $meta_keys );
     422        $this->assertEmpty( get_registered_meta_keys( $type ) );
     423    }
     424
     425    /**
     426     * @ticket 38323
     427     * @dataProvider data_get_types_and_subtypes
     428     */
     429    public function test_get_registered_metadata_with_subtype( $type, $subtype ) {
     430        register_meta( $type, 'registered_key1', array() );
     431
     432        // This will override the above registration for objects of $subtype.
     433        register_meta( $type, 'registered_key1', array(
     434            'object_subtype' => $subtype,
     435            'single'         => true,
     436        ) );
     437
     438        // For testing with $single => false.
     439        register_meta( $type, 'registered_key2', array(
     440            'object_subtype' => $subtype,
     441        ) );
     442
     443        // Register another meta key for a different subtype.
     444        register_meta( $type, 'registered_key3', array(
     445            'object_subtype' => 'other_subtype',
     446        ) );
     447
     448        $object_property_name = $type . '_id';
     449        $object_id = self::$$object_property_name;
     450
     451        add_metadata( $type, $object_id, 'registered_key1', 'value1' );
     452        add_metadata( $type, $object_id, 'registered_key2', 'value2' );
     453        add_metadata( $type, $object_id, 'registered_key3', 'value3' );
     454
     455        $meta = get_registered_metadata( $type, $object_id );
     456
     457        $key1 = get_registered_metadata( $type, $object_id, 'registered_key1' );
     458        $key2 = get_registered_metadata( $type, $object_id, 'registered_key2' );
     459        $key3 = get_registered_metadata( $type, $object_id, 'registered_key3' );
     460
     461        $this->assertSame( array( 'registered_key1', 'registered_key2' ), array_keys( $meta ) );
     462        $this->assertSame( 'value1', $meta['registered_key1'][0] );
     463        $this->assertSame( 'value2', $meta['registered_key2'][0] );
     464
     465        $this->assertSame( 'value1', $key1 );
     466        $this->assertSame( array( 'value2' ), $key2 );
     467        $this->assertFalse( $key3 );
     468    }
     469
     470    /**
     471     * @ticket 38323
     472     * @dataProvider data_get_types_and_subtypes
     473     */
     474    public function test_get_object_subtype( $type, $expected_subtype ) {
     475        $object_property_name = $type . '_id';
     476        $object_id = self::$$object_property_name;
     477
     478        $this->assertSame( $expected_subtype, get_object_subtype( $type, $object_id ) );
     479    }
     480
     481    /**
     482     * @ticket 38323
     483     */
     484    public function test_get_object_subtype_custom() {
     485        add_filter( 'get_object_subtype_customtype', array( $this, 'filter_get_object_subtype_for_customtype' ), 10, 2 );
     486
     487        $subtype_for_3 = get_object_subtype( 'customtype', 3 );
     488        $subtype_for_4 = get_object_subtype( 'customtype', 4 );
     489
     490        $this->assertSame( 'odd', $subtype_for_3 );
     491        $this->assertSame( 'even', $subtype_for_4 );
     492    }
     493
     494    public function filter_get_object_subtype_for_customtype( $subtype, $object_id ) {
     495        if ( $object_id % 2 === 1 ) {
     496            return 'odd';
     497        }
     498
     499        return 'even';
     500    }
     501
     502    public function data_get_types_and_subtypes() {
     503        return array(
     504            array( 'post', 'page' ),
     505            array( 'term', 'category' ),
     506            array( 'comment', 'comment' ),
     507            array( 'user', 'user' ),
     508        );
     509    }
    304510}
  • trunk/tests/phpunit/tests/post/meta.php

    r43339 r43378  
    66 */
    77class Tests_Post_Meta extends WP_UnitTestCase {
     8
     9    private $last_register_meta_call = array(
     10        'object_type' => '',
     11        'meta_key'    => '',
     12        'args'        => array(),
     13    );
    814
    915    protected static $author;
     
    239245
    240246    }
     247
     248    /**
     249     * @ticket 38323
     250     * @dataProvider data_register_post_meta
     251     */
     252    public function test_register_post_meta( $post_type, $meta_key, $args ) {
     253        add_filter( 'register_meta_args', array( $this, 'filter_register_meta_args_set_last_register_meta_call' ), 10, 4 );
     254
     255        register_post_meta( $post_type, $meta_key, $args );
     256
     257        $args['object_subtype'] = $post_type;
     258
     259        // Reset global so subsequent data tests do not get polluted.
     260        $GLOBALS['wp_meta_keys'] = array();
     261
     262        $this->assertEquals( 'post', $this->last_register_meta_call['object_type'] );
     263        $this->assertEquals( $meta_key, $this->last_register_meta_call['meta_key'] );
     264        $this->assertEquals( $args, $this->last_register_meta_call['args'] );
     265    }
     266
     267    public function data_register_post_meta() {
     268        return array(
     269            array( 'post', 'registered_key1', array( 'single' => true ) ),
     270            array( 'page', 'registered_key2', array() ),
     271            array( '', 'registered_key3', array( 'sanitize_callback' => 'absint' ) ),
     272        );
     273    }
     274
     275    public function filter_register_meta_args_set_last_register_meta_call( $args, $defaults, $object_type, $meta_key ) {
     276        $this->last_register_meta_call['object_type'] = $object_type;
     277        $this->last_register_meta_call['meta_key']    = $meta_key;
     278        $this->last_register_meta_call['args']        = $args;
     279
     280        return $args;
     281    }
     282
     283    /**
     284     * @ticket 38323
     285     * @dataProvider data_unregister_post_meta
     286     */
     287    public function test_unregister_post_meta( $post_type, $meta_key ) {
     288        global $wp_meta_keys;
     289
     290        register_post_meta( $post_type, $meta_key, array() );
     291        unregister_post_meta( $post_type, $meta_key );
     292
     293        $actual = $wp_meta_keys;
     294
     295        // Reset global so subsequent data tests do not get polluted.
     296        $wp_meta_keys = array();
     297
     298        $this->assertEmpty( $actual );
     299    }
     300
     301    public function data_unregister_post_meta() {
     302        return array(
     303            array( 'post', 'registered_key1' ),
     304            array( 'page', 'registered_key2' ),
     305            array( '', 'registered_key3' ),
     306        );
     307    }
    241308}
  • trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php

    r43340 r43378  
    1313    protected static $wp_meta_keys_saved;
    1414    protected static $post_id;
     15    protected static $cpt_post_id;
    1516
    1617    public static function wpSetUpBeforeClass( $factory ) {
     18        register_post_type( 'cpt', array(
     19            'show_in_rest' => true,
     20            'supports'     => array( 'custom-fields' ),
     21        ) );
     22
    1723        self::$wp_meta_keys_saved = isset( $GLOBALS['wp_meta_keys'] ) ? $GLOBALS['wp_meta_keys'] : array();
    1824        self::$post_id            = $factory->post->create();
     25        self::$cpt_post_id        = $factory->post->create( array( 'post_type' => 'cpt' ) );
    1926    }
    2027
     
    2229        $GLOBALS['wp_meta_keys'] = self::$wp_meta_keys_saved;
    2330        wp_delete_post( self::$post_id, true );
     31        wp_delete_post( self::$cpt_post_id, true );
     32
     33        unregister_post_type( 'cpt' );
    2434    }
    2535
     
    121131        );
    122132
     133        register_post_type( 'cpt', array(
     134            'show_in_rest' => true,
     135            'supports'     => array( 'custom-fields' ),
     136        ) );
     137
     138        register_post_meta( 'cpt', 'test_cpt_single', array(
     139            'show_in_rest'   => true,
     140            'single'         => true,
     141        ) );
     142
     143        register_post_meta( 'cpt', 'test_cpt_multi', array(
     144            'show_in_rest'   => true,
     145            'single'         => false,
     146        ) );
     147
     148        // Register 'test_single' on subtype to override for bad auth.
     149        register_post_meta( 'cpt', 'test_single', array(
     150            'show_in_rest'   => true,
     151            'single'         => true,
     152            'auth_callback'  => '__return_false',
     153        ) );
     154
    123155        /** @var WP_REST_Server $wp_rest_server */
    124156        global $wp_rest_server;
     
    10491081
    10501082    /**
     1083     * @ticket 38323
     1084     * @dataProvider data_get_subtype_meta_value
     1085     */
     1086    public function test_get_subtype_meta_value( $post_type, $meta_key, $single, $in_post_type ) {
     1087        $post_id  = self::$post_id;
     1088        $endpoint = 'posts';
     1089        if ( 'cpt' === $post_type ) {
     1090            $post_id  = self::$cpt_post_id;
     1091            $endpoint = 'cpt';
     1092        }
     1093
     1094        $meta_value = 'testvalue';
     1095
     1096        add_post_meta( $post_id, $meta_key, $meta_value );
     1097
     1098        $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/%s/%d', $endpoint, $post_id ) );
     1099        $response = rest_get_server()->dispatch( $request );
     1100
     1101        $this->assertEquals( 200, $response->get_status() );
     1102
     1103        $data = $response->get_data();
     1104
     1105        $this->assertArrayHasKey( 'meta', $data );
     1106        $this->assertInternalType( 'array', $data['meta'] );
     1107
     1108        if ( $in_post_type ) {
     1109            $expected_value = $meta_value;
     1110            if ( ! $single ) {
     1111                $expected_value = array( $expected_value );
     1112            }
     1113
     1114            $this->assertArrayHasKey( $meta_key, $data['meta'] );
     1115            $this->assertEquals( $expected_value, $data['meta'][ $meta_key ] );
     1116        } else {
     1117            $this->assertArrayNotHasKey( $meta_key, $data['meta'] );
     1118        }
     1119    }
     1120
     1121    public function data_get_subtype_meta_value() {
     1122        return array(
     1123            array( 'cpt', 'test_cpt_single', true, true ),
     1124            array( 'cpt', 'test_cpt_multi', false, true ),
     1125            array( 'cpt', 'test_single', true, true ),
     1126            array( 'cpt', 'test_multi', false, true ),
     1127            array( 'post', 'test_cpt_single', true, false ),
     1128            array( 'post', 'test_cpt_multi', false, false ),
     1129            array( 'post', 'test_single', true, true ),
     1130            array( 'post', 'test_multi', false, true ),
     1131        );
     1132    }
     1133
     1134    /**
     1135     * @ticket 38323
     1136     * @dataProvider data_set_subtype_meta_value
     1137     */
     1138    public function test_set_subtype_meta_value( $post_type, $meta_key, $single, $in_post_type, $can_write ) {
     1139        $post_id  = self::$post_id;
     1140        $endpoint = 'posts';
     1141        if ( 'cpt' === $post_type ) {
     1142            $post_id  = self::$cpt_post_id;
     1143            $endpoint = 'cpt';
     1144        }
     1145
     1146        $meta_value = 'value_to_set';
     1147
     1148        $this->grant_write_permission();
     1149
     1150        $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/%s/%d', $endpoint, $post_id ) );
     1151        $request->set_body_params( array(
     1152            'meta' => array(
     1153                $meta_key => $meta_value,
     1154            ),
     1155        ) );
     1156
     1157        $response = rest_get_server()->dispatch( $request );
     1158        if ( ! $can_write ) {
     1159            $this->assertEquals( 403, $response->get_status() );
     1160            $this->assertEmpty( get_post_meta( $post_id, $meta_key, $single ) );
     1161            return;
     1162        }
     1163
     1164        $this->assertEquals( 200, $response->get_status() );
     1165
     1166        $data = $response->get_data();
     1167        $this->assertArrayHasKey( 'meta', $data );
     1168        $this->assertInternalType( 'array', $data['meta'] );
     1169
     1170        if ( $in_post_type ) {
     1171            $expected_value = $meta_value;
     1172            if ( ! $single ) {
     1173                $expected_value = array( $expected_value );
     1174            }
     1175
     1176            $this->assertEquals( $expected_value, get_post_meta( $post_id, $meta_key, $single ) );
     1177            $this->assertArrayHasKey( $meta_key, $data['meta'] );
     1178            $this->assertEquals( $expected_value, $data['meta'][ $meta_key ] );
     1179        } else {
     1180            $this->assertEmpty( get_post_meta( $post_id, $meta_key, $single ) );
     1181            $this->assertArrayNotHasKey( $meta_key, $data['meta'] );
     1182        }
     1183    }
     1184
     1185    public function data_set_subtype_meta_value() {
     1186        $data = $this->data_get_subtype_meta_value();
     1187
     1188        foreach ( $data as $index => $dataset ) {
     1189            $can_write = true;
     1190
     1191            // This combination is not writable because of an auth callback of '__return_false'.
     1192            if ( 'cpt' === $dataset[0] && 'test_single' === $dataset[1] ) {
     1193                $can_write = false;
     1194            }
     1195
     1196            $data[ $index ][] = $can_write;
     1197        }
     1198
     1199        return $data;
     1200    }
     1201
     1202    /**
    10511203     * Internal function used to disable an insert query which
    10521204     * will trigger a wpdb error for testing purposes.
  • trunk/tests/phpunit/tests/rest-api/rest-term-meta-fields.php

    r43340 r43378  
    1313    protected static $wp_meta_keys_saved;
    1414    protected static $category_id;
     15    protected static $customtax_term_id;
    1516
    1617    public static function wpSetUpBeforeClass( $factory ) {
     18        register_taxonomy( 'customtax', 'post', array(
     19            'show_in_rest' => true,
     20        ) );
     21
    1722        self::$wp_meta_keys_saved = isset( $GLOBALS['wp_meta_keys'] ) ? $GLOBALS['wp_meta_keys'] : array();
    1823        self::$category_id        = $factory->category->create();
     24        self::$customtax_term_id  = $factory->term->create( array( 'taxonomy' => 'customtax' ) );
    1925    }
    2026
     
    2228        $GLOBALS['wp_meta_keys'] = self::$wp_meta_keys_saved;
    2329        wp_delete_term( self::$category_id, 'category' );
     30        wp_delete_term( self::$customtax_term_id, 'customtax' );
     31
     32        unregister_taxonomy( 'customtax' );
    2433    }
    2534
     
    121130        );
    122131
     132        register_taxonomy( 'customtax', 'post', array(
     133            'show_in_rest' => true,
     134        ) );
     135
     136        register_term_meta( 'customtax', 'test_customtax_single', array(
     137            'show_in_rest'   => true,
     138            'single'         => true,
     139        ) );
     140
     141        register_term_meta( 'customtax', 'test_customtax_multi', array(
     142            'show_in_rest'   => true,
     143            'single'         => false,
     144        ) );
     145
     146        // Register 'test_single' on subtype to override for bad auth.
     147        register_term_meta( 'customtax', 'test_single', array(
     148            'show_in_rest'   => true,
     149            'single'         => true,
     150            'auth_callback'  => '__return_false',
     151        ) );
     152
    123153        /** @var WP_REST_Server $wp_rest_server */
    124154        global $wp_rest_server;
     
    10491079
    10501080    /**
     1081     * @ticket 38323
     1082     * @dataProvider data_get_subtype_meta_value
     1083     */
     1084    public function test_get_subtype_meta_value( $taxonomy, $meta_key, $single, $in_taxonomy ) {
     1085        $term_id  = self::$category_id;
     1086        $endpoint = 'categories';
     1087        if ( 'customtax' === $taxonomy ) {
     1088            $term_id  = self::$customtax_term_id;
     1089            $endpoint = 'customtax';
     1090        }
     1091
     1092        $meta_value = 'testvalue';
     1093
     1094        add_term_meta( $term_id, $meta_key, $meta_value );
     1095
     1096        $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/%s/%d', $endpoint, $term_id ) );
     1097        $response = rest_get_server()->dispatch( $request );
     1098
     1099        $this->assertEquals( 200, $response->get_status() );
     1100
     1101        $data = $response->get_data();
     1102
     1103        $this->assertArrayHasKey( 'meta', $data );
     1104        $this->assertInternalType( 'array', $data['meta'] );
     1105
     1106        if ( $in_taxonomy ) {
     1107            $expected_value = $meta_value;
     1108            if ( ! $single ) {
     1109                $expected_value = array( $expected_value );
     1110            }
     1111
     1112            $this->assertArrayHasKey( $meta_key, $data['meta'] );
     1113            $this->assertEquals( $expected_value, $data['meta'][ $meta_key ] );
     1114        } else {
     1115            $this->assertArrayNotHasKey( $meta_key, $data['meta'] );
     1116        }
     1117    }
     1118
     1119    public function data_get_subtype_meta_value() {
     1120        return array(
     1121            array( 'customtax', 'test_customtax_single', true, true ),
     1122            array( 'customtax', 'test_customtax_multi', false, true ),
     1123            array( 'customtax', 'test_single', true, true ),
     1124            array( 'customtax', 'test_multi', false, true ),
     1125            array( 'category', 'test_customtax_single', true, false ),
     1126            array( 'category', 'test_customtax_multi', false, false ),
     1127            array( 'category', 'test_single', true, true ),
     1128            array( 'category', 'test_multi', false, true ),
     1129        );
     1130    }
     1131
     1132    /**
     1133     * @ticket 38323
     1134     * @dataProvider data_set_subtype_meta_value
     1135     */
     1136    public function test_set_subtype_meta_value( $taxonomy, $meta_key, $single, $in_taxonomy, $can_write ) {
     1137        $term_id  = self::$category_id;
     1138        $endpoint = 'categories';
     1139        if ( 'customtax' === $taxonomy ) {
     1140            $term_id  = self::$customtax_term_id;
     1141            $endpoint = 'customtax';
     1142        }
     1143
     1144        $meta_value = 'value_to_set';
     1145
     1146        $this->grant_write_permission();
     1147
     1148        $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/%s/%d', $endpoint, $term_id ) );
     1149        $request->set_body_params( array(
     1150            'meta' => array(
     1151                $meta_key => $meta_value,
     1152            ),
     1153        ) );
     1154
     1155        $response = rest_get_server()->dispatch( $request );
     1156        if ( ! $can_write ) {
     1157            $this->assertEquals( 403, $response->get_status() );
     1158            $this->assertEmpty( get_term_meta( $term_id, $meta_key, $single ) );
     1159            return;
     1160        }
     1161
     1162        $this->assertEquals( 200, $response->get_status() );
     1163
     1164        $data = $response->get_data();
     1165        $this->assertArrayHasKey( 'meta', $data );
     1166        $this->assertInternalType( 'array', $data['meta'] );
     1167
     1168        if ( $in_taxonomy ) {
     1169            $expected_value = $meta_value;
     1170            if ( ! $single ) {
     1171                $expected_value = array( $expected_value );
     1172            }
     1173
     1174            $this->assertEquals( $expected_value, get_term_meta( $term_id, $meta_key, $single ) );
     1175            $this->assertArrayHasKey( $meta_key, $data['meta'] );
     1176            $this->assertEquals( $expected_value, $data['meta'][ $meta_key ] );
     1177        } else {
     1178            $this->assertEmpty( get_term_meta( $term_id, $meta_key, $single ) );
     1179            $this->assertArrayNotHasKey( $meta_key, $data['meta'] );
     1180        }
     1181    }
     1182
     1183    public function data_set_subtype_meta_value() {
     1184        $data = $this->data_get_subtype_meta_value();
     1185
     1186        foreach ( $data as $index => $dataset ) {
     1187            $can_write = true;
     1188
     1189            // This combination is not writable because of an auth callback of '__return_false'.
     1190            if ( 'customtax' === $dataset[0] && 'test_single' === $dataset[1] ) {
     1191                $can_write = false;
     1192            }
     1193
     1194            $data[ $index ][] = $can_write;
     1195        }
     1196
     1197        return $data;
     1198    }
     1199
     1200    /**
    10511201     * Internal function used to disable an insert query which
    10521202     * will trigger a wpdb error for testing purposes.
  • trunk/tests/phpunit/tests/term/meta.php

    r42343 r43378  
    77 */
    88class Tests_Term_Meta extends WP_UnitTestCase {
     9
     10    private $last_register_meta_call = array(
     11        'object_type' => '',
     12        'meta_key'    => '',
     13        'args'        => array(),
     14    );
     15
    916    public function setUp() {
    1017        parent::setUp();
     
    464471        $q->set( 'cache_results', true );
    465472    }
     473
     474    /**
     475     * @ticket 38323
     476     * @dataProvider data_register_term_meta
     477     */
     478    public function test_register_term_meta( $taxonomy, $meta_key, $args ) {
     479        add_filter( 'register_meta_args', array( $this, 'filter_register_meta_args_set_last_register_meta_call' ), 10, 4 );
     480
     481        register_term_meta( $taxonomy, $meta_key, $args );
     482
     483        $args['object_subtype'] = $taxonomy;
     484
     485        // Reset global so subsequent data tests do not get polluted.
     486        $GLOBALS['wp_meta_keys'] = array();
     487
     488        $this->assertEquals( 'term', $this->last_register_meta_call['object_type'] );
     489        $this->assertEquals( $meta_key, $this->last_register_meta_call['meta_key'] );
     490        $this->assertEquals( $args, $this->last_register_meta_call['args'] );
     491    }
     492
     493    public function data_register_term_meta() {
     494        return array(
     495            array( 'wptests_tax', 'registered_key1', array( 'single' => true ) ),
     496            array( 'category', 'registered_key2', array() ),
     497            array( '', 'registered_key3', array( 'sanitize_callback' => 'absint' ) ),
     498        );
     499    }
     500
     501    public function filter_register_meta_args_set_last_register_meta_call( $args, $defaults, $object_type, $meta_key ) {
     502        $this->last_register_meta_call['object_type'] = $object_type;
     503        $this->last_register_meta_call['meta_key']    = $meta_key;
     504        $this->last_register_meta_call['args']        = $args;
     505
     506        return $args;
     507    }
     508
     509    /**
     510     * @ticket 38323
     511     * @dataProvider data_unregister_term_meta
     512     */
     513    public function test_unregister_term_meta( $taxonomy, $meta_key ) {
     514        global $wp_meta_keys;
     515
     516        register_term_meta( $taxonomy, $meta_key, array() );
     517        unregister_term_meta( $taxonomy, $meta_key );
     518
     519        $actual = $wp_meta_keys;
     520
     521        // Reset global so subsequent data tests do not get polluted.
     522        $wp_meta_keys = array();
     523
     524        $this->assertEmpty( $actual );
     525    }
     526
     527    public function data_unregister_term_meta() {
     528        return array(
     529            array( 'wptests_tax', 'registered_key1' ),
     530            array( 'category', 'registered_key2' ),
     531            array( '', 'registered_key3' ),
     532        );
     533    }
    466534}
Note: See TracChangeset for help on using the changeset viewer.