WP_REST_Meta_Fields

Core class to manage meta values for an object via the REST API.

Defined (1)

The class is defined in the following location(s).

/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php  
  1. abstract class WP_REST_Meta_Fields { 
  2.  
  3. /** 
  4. * Retrieves the object meta type. 
  5. * @since 4.7.0 
  6. * @access protected 
  7. * @return string One of 'post', 'comment', 'term', 'user', or anything 
  8. * else supported by `_get_meta_table()`. 
  9. */ 
  10. abstract protected function get_meta_type(); 
  11.  
  12. /** 
  13. * Retrieves the object type for register_rest_field(). 
  14. * @since 4.7.0 
  15. * @access protected 
  16. * @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`. 
  17. */ 
  18. abstract protected function get_rest_field_type(); 
  19.  
  20. /** 
  21. * Registers the meta field. 
  22. * @since 4.7.0 
  23. * @access public 
  24. * @see register_rest_field() 
  25. */ 
  26. public function register_field() { 
  27. register_rest_field( $this->get_rest_field_type(), 'meta', array( 
  28. 'get_callback' => array( $this, 'get_value' ),  
  29. 'update_callback' => array( $this, 'update_value' ),  
  30. 'schema' => $this->get_field_schema(),  
  31. )); 
  32.  
  33. /** 
  34. * Retrieves the meta field value. 
  35. * @since 4.7.0 
  36. * @access public 
  37. * @param int $object_id Object ID to fetch meta for. 
  38. * @param WP_REST_Request $request Full details about the request. 
  39. * @return WP_Error|object Object containing the meta values by name, otherwise WP_Error object. 
  40. */ 
  41. public function get_value( $object_id, $request ) { 
  42. $fields = $this->get_registered_fields(); 
  43. $response = array(); 
  44.  
  45. foreach ( $fields as $meta_key => $args ) { 
  46. $name = $args['name']; 
  47. $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false ); 
  48. if ( $args['single'] ) { 
  49. if ( empty( $all_values ) ) { 
  50. $value = $args['schema']['default']; 
  51. } else { 
  52. $value = $all_values[0]; 
  53. $value = $this->prepare_value_for_response( $value, $request, $args ); 
  54. } else { 
  55. $value = array(); 
  56. foreach ( $all_values as $row ) { 
  57. $value[] = $this->prepare_value_for_response( $row, $request, $args ); 
  58.  
  59. $response[ $name ] = $value; 
  60.  
  61. return $response; 
  62.  
  63. /** 
  64. * Prepares a meta value for a response. 
  65. * This is required because some native types cannot be stored correctly 
  66. * in the database, such as booleans. We need to cast back to the relevant 
  67. * type before passing back to JSON. 
  68. * @since 4.7.0 
  69. * @access protected 
  70. * @param mixed $value Meta value to prepare. 
  71. * @param WP_REST_Request $request Current request object. 
  72. * @param array $args Options for the field. 
  73. * @return mixed Prepared value. 
  74. */ 
  75. protected function prepare_value_for_response( $value, $request, $args ) { 
  76. if ( ! empty( $args['prepare_callback'] ) ) { 
  77. $value = call_user_func( $args['prepare_callback'], $value, $request, $args ); 
  78.  
  79. return $value; 
  80.  
  81. /** 
  82. * Updates meta values. 
  83. * @since 4.7.0 
  84. * @access public 
  85. * @param array $meta Array of meta parsed from the request. 
  86. * @param int $object_id Object ID to fetch meta for. 
  87. * @return WP_Error|null WP_Error if one occurs, null on success. 
  88. */ 
  89. public function update_value( $meta, $object_id ) { 
  90. $fields = $this->get_registered_fields(); 
  91. foreach ( $fields as $meta_key => $args ) { 
  92. $name = $args['name']; 
  93. if ( ! array_key_exists( $name, $meta ) ) { 
  94. continue; 
  95.  
  96. /** 
  97. * A null value means reset the field, which is essentially deleting it 
  98. * from the database and then relying on the default value. 
  99. */ 
  100. if ( is_null( $meta[ $name ] ) ) { 
  101. $result = $this->delete_meta_value( $object_id, $meta_key, $name ); 
  102. if ( is_wp_error( $result ) ) { 
  103. return $result; 
  104. continue; 
  105.  
  106. $is_valid = rest_validate_value_from_schema( $meta[ $name ], $args['schema'], 'meta.' . $name ); 
  107. if ( is_wp_error( $is_valid ) ) { 
  108. $is_valid->add_data( array( 'status' => 400 ) ); 
  109. return $is_valid; 
  110.  
  111. $value = rest_sanitize_value_from_schema( $meta[ $name ], $args['schema'] ); 
  112.  
  113. if ( $args['single'] ) { 
  114. $result = $this->update_meta_value( $object_id, $meta_key, $name, $value ); 
  115. } else { 
  116. $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value ); 
  117.  
  118. if ( is_wp_error( $result ) ) { 
  119. return $result; 
  120.  
  121. return null; 
  122.  
  123. /** 
  124. * Deletes a meta value for an object. 
  125. * @since 4.7.0 
  126. * @access protected 
  127. * @param int $object_id Object ID the field belongs to. 
  128. * @param string $meta_key Key for the field. 
  129. * @param string $name Name for the field that is exposed in the REST API. 
  130. * @return bool|WP_Error True if meta field is deleted, WP_Error otherwise. 
  131. */ 
  132. protected function delete_meta_value( $object_id, $meta_key, $name ) { 
  133. $meta_type = $this->get_meta_type(); 
  134. if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) { 
  135. return new WP_Error( 
  136. 'rest_cannot_delete',  
  137. /** translators: %s: custom field key */ 
  138. sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),  
  139. array( 'key' => $name, 'status' => rest_authorization_required_code() ) 
  140. ); 
  141.  
  142. if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) { 
  143. return new WP_Error( 
  144. 'rest_meta_database_error',  
  145. __( 'Could not delete meta value from database.' ),  
  146. array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 
  147. ); 
  148.  
  149. return true; 
  150.  
  151. /** 
  152. * Updates multiple meta values for an object. 
  153. * Alters the list of values in the database to match the list of provided values. 
  154. * @since 4.7.0 
  155. * @access protected 
  156. * @param int $object_id Object ID to update. 
  157. * @param string $meta_key Key for the custom field. 
  158. * @param string $name Name for the field that is exposed in the REST API. 
  159. * @param array $values List of values to update to. 
  160. * @return bool|WP_Error True if meta fields are updated, WP_Error otherwise. 
  161. */ 
  162. protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) { 
  163. $meta_type = $this->get_meta_type(); 
  164. if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { 
  165. return new WP_Error( 
  166. 'rest_cannot_update',  
  167. /** translators: %s: custom field key */ 
  168. sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),  
  169. array( 'key' => $name, 'status' => rest_authorization_required_code() ) 
  170. ); 
  171.  
  172. $current = get_metadata( $meta_type, $object_id, $meta_key, false ); 
  173.  
  174. $to_remove = $current; 
  175. $to_add = $values; 
  176.  
  177. foreach ( $to_add as $add_key => $value ) { 
  178. $remove_keys = array_keys( $to_remove, $value, true ); 
  179.  
  180. if ( empty( $remove_keys ) ) { 
  181. continue; 
  182.  
  183. if ( count( $remove_keys ) > 1 ) { 
  184. // To remove, we need to remove first, then add, so don't touch. 
  185. continue; 
  186.  
  187. $remove_key = $remove_keys[0]; 
  188.  
  189. unset( $to_remove[ $remove_key ] ); 
  190. unset( $to_add[ $add_key ] ); 
  191.  
  192. // `delete_metadata` removes _all_ instances of the value, so only call once. 
  193. $to_remove = array_unique( $to_remove ); 
  194.  
  195. foreach ( $to_remove as $value ) { 
  196. if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { 
  197. return new WP_Error( 
  198. 'rest_meta_database_error',  
  199. __( 'Could not update meta value in database.' ),  
  200. array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 
  201. ); 
  202.  
  203. foreach ( $to_add as $value ) { 
  204. if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { 
  205. return new WP_Error( 
  206. 'rest_meta_database_error',  
  207. __( 'Could not update meta value in database.' ),  
  208. array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 
  209. ); 
  210.  
  211. return true; 
  212.  
  213. /** 
  214. * Updates a meta value for an object. 
  215. * @since 4.7.0 
  216. * @access protected 
  217. * @param int $object_id Object ID to update. 
  218. * @param string $meta_key Key for the custom field. 
  219. * @param string $name Name for the field that is exposed in the REST API. 
  220. * @param mixed $value Updated value. 
  221. * @return bool|WP_Error True if the meta field was updated, WP_Error otherwise. 
  222. */ 
  223. protected function update_meta_value( $object_id, $meta_key, $name, $value ) { 
  224. $meta_type = $this->get_meta_type(); 
  225. if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { 
  226. return new WP_Error( 
  227. 'rest_cannot_update',  
  228. /** translators: %s: custom field key */ 
  229. sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),  
  230. array( 'key' => $name, 'status' => rest_authorization_required_code() ) 
  231. ); 
  232.  
  233. $meta_key = wp_slash( $meta_key ); 
  234. $meta_value = wp_slash( $value ); 
  235.  
  236. // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. 
  237. $old_value = get_metadata( $meta_type, $object_id, $meta_key ); 
  238.  
  239. if ( 1 === count( $old_value ) ) { 
  240. if ( $old_value[0] === $meta_value ) { 
  241. return true; 
  242.  
  243. if ( ! update_metadata( $meta_type, $object_id, $meta_key, $meta_value ) ) { 
  244. return new WP_Error( 
  245. 'rest_meta_database_error',  
  246. __( 'Could not update meta value in database.' ),  
  247. array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR ) 
  248. ); 
  249.  
  250. return true; 
  251.  
  252. /** 
  253. * Retrieves all the registered meta fields. 
  254. * @since 4.7.0 
  255. * @access protected 
  256. * @return array Registered fields. 
  257. */ 
  258. protected function get_registered_fields() { 
  259. $registered = array(); 
  260.  
  261. foreach ( get_registered_meta_keys( $this->get_meta_type() ) as $name => $args ) { 
  262. if ( empty( $args['show_in_rest'] ) ) { 
  263. continue; 
  264.  
  265. $rest_args = array(); 
  266.  
  267. if ( is_array( $args['show_in_rest'] ) ) { 
  268. $rest_args = $args['show_in_rest']; 
  269.  
  270. $default_args = array( 
  271. 'name' => $name,  
  272. 'single' => $args['single'],  
  273. 'type' => ! empty( $args['type'] ) ? $args['type'] : null,  
  274. 'schema' => array(),  
  275. 'prepare_callback' => array( $this, 'prepare_value' ),  
  276. ); 
  277.  
  278. $default_schema = array( 
  279. 'type' => $default_args['type'],  
  280. 'description' => empty( $args['description'] ) ? '' : $args['description'],  
  281. 'default' => isset( $args['default'] ) ? $args['default'] : null,  
  282. ); 
  283.  
  284. $rest_args = array_merge( $default_args, $rest_args ); 
  285. $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); 
  286.  
  287. $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null; 
  288. $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type; 
  289.  
  290. if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) { 
  291. continue; 
  292.  
  293. if ( empty( $rest_args['single'] ) ) { 
  294. $rest_args['schema']['items'] = array( 
  295. 'type' => $rest_args['type'],  
  296. ); 
  297. $rest_args['schema']['type'] = 'array'; 
  298.  
  299. $registered[ $name ] = $rest_args; 
  300.  
  301. return $registered; 
  302.  
  303. /** 
  304. * Retrieves the object's meta schema, conforming to JSON Schema. 
  305. * @since 4.7.0 
  306. * @access protected 
  307. * @return array Field schema data. 
  308. */ 
  309. public function get_field_schema() { 
  310. $fields = $this->get_registered_fields(); 
  311.  
  312. $schema = array( 
  313. 'description' => __( 'Meta fields.' ),  
  314. 'type' => 'object',  
  315. 'context' => array( 'view', 'edit' ),  
  316. 'properties' => array(),  
  317. 'arg_options' => array( 
  318. 'sanitize_callback' => null,  
  319. 'validate_callback' => array( $this, 'check_meta_is_array' ),  
  320. ),  
  321. ); 
  322.  
  323. foreach ( $fields as $args ) { 
  324. $schema['properties'][ $args['name'] ] = $args['schema']; 
  325.  
  326. return $schema; 
  327.  
  328. /** 
  329. * Prepares a meta value for output. 
  330. * Default preparation for meta fields. Override by passing the 
  331. * `prepare_callback` in your `show_in_rest` options. 
  332. * @since 4.7.0 
  333. * @access public 
  334. * @param mixed $value Meta value from the database. 
  335. * @param WP_REST_Request $request Request object. 
  336. * @param array $args REST-specific options for the meta key. 
  337. * @return mixed Value prepared for output. If a non-JsonSerializable object, null. 
  338. */ 
  339. public static function prepare_value( $value, $request, $args ) { 
  340. $type = $args['schema']['type']; 
  341.  
  342. // For multi-value fields, check the item type instead. 
  343. if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) { 
  344. $type = $args['schema']['items']['type']; 
  345.  
  346. switch ( $type ) { 
  347. case 'string': 
  348. $value = (string) $value; 
  349. break; 
  350. case 'integer': 
  351. $value = (int) $value; 
  352. break; 
  353. case 'number': 
  354. $value = (float) $value; 
  355. break; 
  356. case 'boolean': 
  357. $value = (bool) $value; 
  358. break; 
  359.  
  360. // Don't allow objects to be output. 
  361. if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) { 
  362. return null; 
  363.  
  364. return $value; 
  365.  
  366. /** 
  367. * Check the 'meta' value of a request is an associative array. 
  368. * @since 4.7.0 
  369. * @access public 
  370. * @param mixed $value The meta value submitted in the request. 
  371. * @param WP_REST_Request $request Full details about the request. 
  372. * @param string $param The parameter name. 
  373. * @return WP_Error|string The meta array, if valid, otherwise an error. 
  374. */ 
  375. public function check_meta_is_array( $value, $request, $param ) { 
  376. if ( ! is_array( $value ) ) { 
  377. return false; 
  378.  
  379. return $value;