Make WordPress Core

Ticket #38323: 38323.12.diff

File 38323.12.diff, 57.0 KB (added by kadamwhite, 6 years ago)

Improve performance of object subtype lookup, and reinstate comment_type support

  • src/wp-includes/capabilities.php

    diff --git src/wp-includes/capabilities.php src/wp-includes/capabilities.php
    index 608ff40625..e574b3f809 100644
    function map_meta_cap( $cap, $user_id ) { 
    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                                         }
    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;
     284                        $object_subtype = get_object_subtype( $object_type, $object_id );
    311285
    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                        }
    function map_meta_cap( $cap, $user_id ) { 
    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 ) {
    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;
    382370                case 'edit_comment':
  • src/wp-includes/meta.php

    diff --git src/wp-includes/meta.php src/wp-includes/meta.php
    index 7959be5576..9aa6a37fbc 100644
    function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = 
    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.
    function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_ 
    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
    function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_ 
    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.
    function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = 
    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.
    function is_protected_meta( $meta_key, $meta_type = null ) { 
    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.
    function is_protected_meta( $meta_key, $meta_type = null ) { 
    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         *
    function sanitize_meta( $meta_key, $meta_value, $object_type ) { 
    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.
    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'.
    10101046 *     @type string $description       A description of the data attached to this meta key.
    function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { 
    10271063        }
    10281064
    10291065        $defaults = array(
     1066                'object_subtype'    => '',
    10301067                'type'              => 'string',
    10311068                'description'       => '',
    10321069                'single'            => false,
    function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { 
    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 ) ) {
    function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { 
    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'] );
     1138
     1139                $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args;
    10911140
    10921141                return true;
    10931142        }
    function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { 
    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;
    1110 
    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         }
     1160function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) {
     1161        $meta_keys = get_registered_meta_keys( $object_type, $object_subtype );
    11221162
    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        }
    function unregister_meta_key( $object_type, $meta_key ) { 
    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)
    function get_registered_meta_keys( $object_type ) { 
    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'] );
    function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) { 
    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/**
    function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) { 
    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_type = get_post_type( $object_type );
     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                        if ( ! empty( $comment->comment_type ) ) {
     1338                                $object_subtype = $comment->comment_type;
     1339                                break;
     1340                        }
     1341
     1342                        $object_subtype = 'comment';
     1343                        break;
     1344
     1345                case 'user':
     1346                        $user = get_user_by( 'id', $object_id );
     1347                        if ( ! $user ) {
     1348                                break;
     1349                        }
     1350
     1351                        $object_subtype = 'user';
     1352                        break;
    12411353        }
    12421354
    1243         return $args;
     1355        /**
     1356         * Filters the object subtype identifier for a non standard object type.
     1357         *
     1358         * The dynamic portion of the hook, `$object_type`, refers to the object
     1359         * type (post, comment, term, or user).
     1360         *
     1361         * @since 5.0.0
     1362         *
     1363         * @param string $object_subtype Empty string to override.
     1364         * @param int    $object_id      ID of the object to get the subtype for.
     1365         */
     1366        return apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id );
    12441367}
  • src/wp-includes/post.php

    diff --git src/wp-includes/post.php src/wp-includes/post.php
    index 98ad57fbd0..3ae08c1319 100644
    function delete_post_meta_by_key( $post_meta_key ) { 
    19891989        return $deleted;
    19901990}
    19911991
     1992/**
     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
    19922025/**
    19932026 * Retrieve post meta fields, based on post ID.
    19942027 *
  • src/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php

    diff --git src/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php src/wp-includes/rest-api/fields/class-wp-rest-comment-meta-fields.php
    index b22206953a..d006e02464 100644
    class WP_REST_Comment_Meta_Fields extends WP_REST_Meta_Fields { 
    2727                return 'comment';
    2828        }
    2929
     30        /**
     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
    3041        /**
    3142         * Retrieves the type for register_rest_field() in the context of comments.
    3243         *
  • src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php

    diff --git src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
    index e0b41440a9..b85bbe9e9f 100644
    abstract class WP_REST_Meta_Fields { 
    2424         */
    2525        abstract protected function get_meta_type();
    2626
     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        }
     37
    2738        /**
    2839         * Retrieves the object type for register_rest_field().
    2940         *
    abstract class WP_REST_Meta_Fields { 
    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

    diff --git src/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php src/wp-includes/rest-api/fields/class-wp-rest-post-meta-fields.php
    index e15364246e..d1c3ad6ad9 100644
    class WP_REST_Post_Meta_Fields extends WP_REST_Meta_Fields { 
    4646                return 'post';
    4747        }
    4848
     49        /**
     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
    4960        /**
    5061         * Retrieves the type for register_rest_field().
    5162         *
  • src/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php

    diff --git src/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php src/wp-includes/rest-api/fields/class-wp-rest-term-meta-fields.php
    index aa876ccb8e..be9f67bf49 100644
    class WP_REST_Term_Meta_Fields extends WP_REST_Meta_Fields { 
    4646                return 'term';
    4747        }
    4848
     49        /**
     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
    4960        /**
    5061         * Retrieves the type for register_rest_field().
    5162         *
  • src/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php

    diff --git src/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php src/wp-includes/rest-api/fields/class-wp-rest-user-meta-fields.php
    index 6c5494f521..5af3c76a9c 100644
    class WP_REST_User_Meta_Fields extends WP_REST_Meta_Fields { 
    2727                return 'user';
    2828        }
    2929
     30        /**
     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
    3041        /**
    3142         * Retrieves the type for register_rest_field().
    3243         *
  • src/wp-includes/taxonomy.php

    diff --git src/wp-includes/taxonomy.php src/wp-includes/taxonomy.php
    index 102345fd3c..2df5ac6b41 100644
    function has_term_meta( $term_id ) { 
    13221322        return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, term_id FROM $wpdb->termmeta WHERE term_id = %d ORDER BY meta_key,meta_id", $term_id ), ARRAY_A );
    13231323}
    13241324
     1325/**
     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
    13251358/**
    13261359 * Determines whether a term exists.
    13271360 *
  • tests/phpunit/tests/meta/registerMeta.php

    diff --git tests/phpunit/tests/meta/registerMeta.php tests/phpunit/tests/meta/registerMeta.php
    index 4eced6e128..489ac5fa8a 100644
     
    33 * @group meta
    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
    1226        public function _old_sanitize_meta_cb( $meta_value, $meta_key, $meta_type ) {
    class Tests_Meta_Register_Meta extends WP_UnitTestCase { 
    7488
    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
    91107        public function test_register_meta_with_term_object_type_populates_wp_meta_keys() {
    class Tests_Meta_Register_Meta extends WP_UnitTestCase { 
    96112
    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
    113131        public function test_register_meta_with_deprecated_sanitize_callback_does_not_populate_wp_meta_keys() {
    class Tests_Meta_Register_Meta extends WP_UnitTestCase { 
    148166
    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                        ),
    160180                );
    class Tests_Meta_Register_Meta extends WP_UnitTestCase { 
    301321
    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}
  • tests/phpunit/tests/post/meta.php

    diff --git tests/phpunit/tests/post/meta.php tests/phpunit/tests/post/meta.php
    index f29c84506b..5b4a23e2f1 100644
     
    66 */
    77class Tests_Post_Meta extends WP_UnitTestCase {
    88
     9        private $last_register_meta_call = array(
     10                'object_type' => '',
     11                'meta_key'    => '',
     12                'args'        => array(),
     13        );
     14
    915        protected static $author;
    1016        protected static $post_id;
    1117        protected static $post_id_2;
    class Tests_Post_Meta extends WP_UnitTestCase { 
    238244                $this->assertEquals( $funky_meta, get_post_meta( self::$post_id, 'test_funky_post_meta', true ) );
    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}
  • tests/phpunit/tests/rest-api/rest-post-meta-fields.php

    diff --git tests/phpunit/tests/rest-api/rest-post-meta-fields.php tests/phpunit/tests/rest-api/rest-post-meta-fields.php
    index ce04068f87..db5829f6ef 100644
     
    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 $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
    2128        public static function wpTearDownAfterClass() {
    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
    2636        public function setUp() {
    class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase { 
    120130                        )
    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;
    125157                $wp_rest_server = new Spy_REST_Server;
    class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase { 
    10471079                $this->assertArrayNotHasKey( 'test_no_type', $meta_schema );
    10481080        }
    10491081
     1082        /**
     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
    10501202        /**
    10511203         * Internal function used to disable an insert query which
    10521204         * will trigger a wpdb error for testing purposes.
  • tests/phpunit/tests/rest-api/rest-term-meta-fields.php

    diff --git tests/phpunit/tests/rest-api/rest-term-meta-fields.php tests/phpunit/tests/rest-api/rest-term-meta-fields.php
    index 6680dde255..c5782ab8f5 100644
     
    1212class WP_Test_REST_Term_Meta_Fields extends WP_Test_REST_TestCase {
    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
    2127        public static function wpTearDownAfterClass() {
    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
    2635        public function setUp() {
    class WP_Test_REST_Term_Meta_Fields extends WP_Test_REST_TestCase { 
    120129                        )
    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;
    125155                $wp_rest_server = new Spy_REST_Server;
    class WP_Test_REST_Term_Meta_Fields extends WP_Test_REST_TestCase { 
    10471077                $this->assertArrayNotHasKey( 'test_no_type', $meta_schema );
    10481078        }
    10491079
     1080        /**
     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
    10501200        /**
    10511201         * Internal function used to disable an insert query which
    10521202         * will trigger a wpdb error for testing purposes.
  • tests/phpunit/tests/term/meta.php

    diff --git tests/phpunit/tests/term/meta.php tests/phpunit/tests/term/meta.php
    index d81a8ab5d8..d3fc2fe347 100644
     
    66 * @ticket 10142
    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();
    1118                register_taxonomy( 'wptests_tax', 'post' );
    class Tests_Term_Meta extends WP_UnitTestCase { 
    463470        public static function set_cache_results( $q ) {
    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}