Brak opisu

class-wp-rest-meta-fields.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. <?php
  2. /**
  3. * REST API: WP_REST_Meta_Fields class
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 4.7.0
  8. */
  9. /**
  10. * Core class to manage meta values for an object via the REST API.
  11. *
  12. * @since 4.7.0
  13. */
  14. abstract class WP_REST_Meta_Fields {
  15. /**
  16. * Retrieves the object meta type.
  17. *
  18. * @since 4.7.0
  19. *
  20. * @return string One of 'post', 'comment', 'term', 'user', or anything
  21. * else supported by `_get_meta_table()`.
  22. */
  23. abstract protected function get_meta_type();
  24. /**
  25. * Retrieves the object meta subtype.
  26. *
  27. * @since 4.9.8
  28. *
  29. * @return string Subtype for the meta type, or empty string if no specific subtype.
  30. */
  31. protected function get_meta_subtype() {
  32. return '';
  33. }
  34. /**
  35. * Retrieves the object type for register_rest_field().
  36. *
  37. * @since 4.7.0
  38. *
  39. * @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`.
  40. */
  41. abstract protected function get_rest_field_type();
  42. /**
  43. * Registers the meta field.
  44. *
  45. * @since 4.7.0
  46. * @deprecated 5.6.0
  47. *
  48. * @see register_rest_field()
  49. */
  50. public function register_field() {
  51. _deprecated_function( __METHOD__, '5.6.0' );
  52. register_rest_field(
  53. $this->get_rest_field_type(),
  54. 'meta',
  55. array(
  56. 'get_callback' => array( $this, 'get_value' ),
  57. 'update_callback' => array( $this, 'update_value' ),
  58. 'schema' => $this->get_field_schema(),
  59. )
  60. );
  61. }
  62. /**
  63. * Retrieves the meta field value.
  64. *
  65. * @since 4.7.0
  66. *
  67. * @param int $object_id Object ID to fetch meta for.
  68. * @param WP_REST_Request $request Full details about the request.
  69. * @return array Array containing the meta values keyed by name.
  70. */
  71. public function get_value( $object_id, $request ) {
  72. $fields = $this->get_registered_fields();
  73. $response = array();
  74. foreach ( $fields as $meta_key => $args ) {
  75. $name = $args['name'];
  76. $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false );
  77. if ( $args['single'] ) {
  78. if ( empty( $all_values ) ) {
  79. $value = $args['schema']['default'];
  80. } else {
  81. $value = $all_values[0];
  82. }
  83. $value = $this->prepare_value_for_response( $value, $request, $args );
  84. } else {
  85. $value = array();
  86. if ( is_array( $all_values ) ) {
  87. foreach ( $all_values as $row ) {
  88. $value[] = $this->prepare_value_for_response( $row, $request, $args );
  89. }
  90. }
  91. }
  92. $response[ $name ] = $value;
  93. }
  94. return $response;
  95. }
  96. /**
  97. * Prepares a meta value for a response.
  98. *
  99. * This is required because some native types cannot be stored correctly
  100. * in the database, such as booleans. We need to cast back to the relevant
  101. * type before passing back to JSON.
  102. *
  103. * @since 4.7.0
  104. *
  105. * @param mixed $value Meta value to prepare.
  106. * @param WP_REST_Request $request Current request object.
  107. * @param array $args Options for the field.
  108. * @return mixed Prepared value.
  109. */
  110. protected function prepare_value_for_response( $value, $request, $args ) {
  111. if ( ! empty( $args['prepare_callback'] ) ) {
  112. $value = call_user_func( $args['prepare_callback'], $value, $request, $args );
  113. }
  114. return $value;
  115. }
  116. /**
  117. * Updates meta values.
  118. *
  119. * @since 4.7.0
  120. *
  121. * @param array $meta Array of meta parsed from the request.
  122. * @param int $object_id Object ID to fetch meta for.
  123. * @return null|WP_Error Null on success, WP_Error object on failure.
  124. */
  125. public function update_value( $meta, $object_id ) {
  126. $fields = $this->get_registered_fields();
  127. foreach ( $fields as $meta_key => $args ) {
  128. $name = $args['name'];
  129. if ( ! array_key_exists( $name, $meta ) ) {
  130. continue;
  131. }
  132. $value = $meta[ $name ];
  133. /*
  134. * A null value means reset the field, which is essentially deleting it
  135. * from the database and then relying on the default value.
  136. *
  137. * Non-single meta can also be removed by passing an empty array.
  138. */
  139. if ( is_null( $value ) || ( array() === $value && ! $args['single'] ) ) {
  140. $args = $this->get_registered_fields()[ $meta_key ];
  141. if ( $args['single'] ) {
  142. $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true );
  143. if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) {
  144. return new WP_Error(
  145. 'rest_invalid_stored_value',
  146. /* translators: %s: Custom field key. */
  147. sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
  148. array( 'status' => 500 )
  149. );
  150. }
  151. }
  152. $result = $this->delete_meta_value( $object_id, $meta_key, $name );
  153. if ( is_wp_error( $result ) ) {
  154. return $result;
  155. }
  156. continue;
  157. }
  158. if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) {
  159. return new WP_Error(
  160. 'rest_invalid_stored_value',
  161. /* translators: %s: Custom field key. */
  162. sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
  163. array( 'status' => 500 )
  164. );
  165. }
  166. $is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name );
  167. if ( is_wp_error( $is_valid ) ) {
  168. $is_valid->add_data( array( 'status' => 400 ) );
  169. return $is_valid;
  170. }
  171. $value = rest_sanitize_value_from_schema( $value, $args['schema'] );
  172. if ( $args['single'] ) {
  173. $result = $this->update_meta_value( $object_id, $meta_key, $name, $value );
  174. } else {
  175. $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value );
  176. }
  177. if ( is_wp_error( $result ) ) {
  178. return $result;
  179. }
  180. }
  181. return null;
  182. }
  183. /**
  184. * Deletes a meta value for an object.
  185. *
  186. * @since 4.7.0
  187. *
  188. * @param int $object_id Object ID the field belongs to.
  189. * @param string $meta_key Key for the field.
  190. * @param string $name Name for the field that is exposed in the REST API.
  191. * @return true|WP_Error True if meta field is deleted, WP_Error otherwise.
  192. */
  193. protected function delete_meta_value( $object_id, $meta_key, $name ) {
  194. $meta_type = $this->get_meta_type();
  195. if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) {
  196. return new WP_Error(
  197. 'rest_cannot_delete',
  198. /* translators: %s: Custom field key. */
  199. sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
  200. array(
  201. 'key' => $name,
  202. 'status' => rest_authorization_required_code(),
  203. )
  204. );
  205. }
  206. if ( null === get_metadata_raw( $meta_type, $object_id, wp_slash( $meta_key ) ) ) {
  207. return true;
  208. }
  209. if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) {
  210. return new WP_Error(
  211. 'rest_meta_database_error',
  212. __( 'Could not delete meta value from database.' ),
  213. array(
  214. 'key' => $name,
  215. 'status' => WP_Http::INTERNAL_SERVER_ERROR,
  216. )
  217. );
  218. }
  219. return true;
  220. }
  221. /**
  222. * Updates multiple meta values for an object.
  223. *
  224. * Alters the list of values in the database to match the list of provided values.
  225. *
  226. * @since 4.7.0
  227. *
  228. * @param int $object_id Object ID to update.
  229. * @param string $meta_key Key for the custom field.
  230. * @param string $name Name for the field that is exposed in the REST API.
  231. * @param array $values List of values to update to.
  232. * @return true|WP_Error True if meta fields are updated, WP_Error otherwise.
  233. */
  234. protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) {
  235. $meta_type = $this->get_meta_type();
  236. if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) {
  237. return new WP_Error(
  238. 'rest_cannot_update',
  239. /* translators: %s: Custom field key. */
  240. sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
  241. array(
  242. 'key' => $name,
  243. 'status' => rest_authorization_required_code(),
  244. )
  245. );
  246. }
  247. $current_values = get_metadata( $meta_type, $object_id, $meta_key, false );
  248. $subtype = get_object_subtype( $meta_type, $object_id );
  249. if ( ! is_array( $current_values ) ) {
  250. $current_values = array();
  251. }
  252. $to_remove = $current_values;
  253. $to_add = $values;
  254. foreach ( $to_add as $add_key => $value ) {
  255. $remove_keys = array_keys(
  256. array_filter(
  257. $current_values,
  258. function ( $stored_value ) use ( $meta_key, $subtype, $value ) {
  259. return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value );
  260. }
  261. )
  262. );
  263. if ( empty( $remove_keys ) ) {
  264. continue;
  265. }
  266. if ( count( $remove_keys ) > 1 ) {
  267. // To remove, we need to remove first, then add, so don't touch.
  268. continue;
  269. }
  270. $remove_key = $remove_keys[0];
  271. unset( $to_remove[ $remove_key ] );
  272. unset( $to_add[ $add_key ] );
  273. }
  274. /*
  275. * `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise,
  276. * `delete_metadata` will return false for subsequent calls of the same value.
  277. * Use serialization to produce a predictable string that can be used by array_unique.
  278. */
  279. $to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) );
  280. foreach ( $to_remove as $value ) {
  281. if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
  282. return new WP_Error(
  283. 'rest_meta_database_error',
  284. /* translators: %s: Custom field key. */
  285. sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
  286. array(
  287. 'key' => $name,
  288. 'status' => WP_Http::INTERNAL_SERVER_ERROR,
  289. )
  290. );
  291. }
  292. }
  293. foreach ( $to_add as $value ) {
  294. if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
  295. return new WP_Error(
  296. 'rest_meta_database_error',
  297. /* translators: %s: Custom field key. */
  298. sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
  299. array(
  300. 'key' => $name,
  301. 'status' => WP_Http::INTERNAL_SERVER_ERROR,
  302. )
  303. );
  304. }
  305. }
  306. return true;
  307. }
  308. /**
  309. * Updates a meta value for an object.
  310. *
  311. * @since 4.7.0
  312. *
  313. * @param int $object_id Object ID to update.
  314. * @param string $meta_key Key for the custom field.
  315. * @param string $name Name for the field that is exposed in the REST API.
  316. * @param mixed $value Updated value.
  317. * @return true|WP_Error True if the meta field was updated, WP_Error otherwise.
  318. */
  319. protected function update_meta_value( $object_id, $meta_key, $name, $value ) {
  320. $meta_type = $this->get_meta_type();
  321. if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) {
  322. return new WP_Error(
  323. 'rest_cannot_update',
  324. /* translators: %s: Custom field key. */
  325. sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
  326. array(
  327. 'key' => $name,
  328. 'status' => rest_authorization_required_code(),
  329. )
  330. );
  331. }
  332. // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false.
  333. $old_value = get_metadata( $meta_type, $object_id, $meta_key );
  334. $subtype = get_object_subtype( $meta_type, $object_id );
  335. if ( is_array( $old_value ) && 1 === count( $old_value )
  336. && $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value )
  337. ) {
  338. return true;
  339. }
  340. if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
  341. return new WP_Error(
  342. 'rest_meta_database_error',
  343. /* translators: %s: Custom field key. */
  344. sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
  345. array(
  346. 'key' => $name,
  347. 'status' => WP_Http::INTERNAL_SERVER_ERROR,
  348. )
  349. );
  350. }
  351. return true;
  352. }
  353. /**
  354. * Checks if the user provided value is equivalent to a stored value for the given meta key.
  355. *
  356. * @since 5.5.0
  357. *
  358. * @param string $meta_key The meta key being checked.
  359. * @param string $subtype The object subtype.
  360. * @param mixed $stored_value The currently stored value retrieved from get_metadata().
  361. * @param mixed $user_value The value provided by the user.
  362. * @return bool
  363. */
  364. protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) {
  365. $args = $this->get_registered_fields()[ $meta_key ];
  366. $sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype );
  367. if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
  368. // The return value of get_metadata will always be a string for scalar types.
  369. $sanitized = (string) $sanitized;
  370. }
  371. return $sanitized === $stored_value;
  372. }
  373. /**
  374. * Retrieves all the registered meta fields.
  375. *
  376. * @since 4.7.0
  377. *
  378. * @return array Registered fields.
  379. */
  380. protected function get_registered_fields() {
  381. $registered = array();
  382. $meta_type = $this->get_meta_type();
  383. $meta_subtype = $this->get_meta_subtype();
  384. $meta_keys = get_registered_meta_keys( $meta_type );
  385. if ( ! empty( $meta_subtype ) ) {
  386. $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $meta_type, $meta_subtype ) );
  387. }
  388. foreach ( $meta_keys as $name => $args ) {
  389. if ( empty( $args['show_in_rest'] ) ) {
  390. continue;
  391. }
  392. $rest_args = array();
  393. if ( is_array( $args['show_in_rest'] ) ) {
  394. $rest_args = $args['show_in_rest'];
  395. }
  396. $default_args = array(
  397. 'name' => $name,
  398. 'single' => $args['single'],
  399. 'type' => ! empty( $args['type'] ) ? $args['type'] : null,
  400. 'schema' => array(),
  401. 'prepare_callback' => array( $this, 'prepare_value' ),
  402. );
  403. $default_schema = array(
  404. 'type' => $default_args['type'],
  405. 'description' => empty( $args['description'] ) ? '' : $args['description'],
  406. 'default' => isset( $args['default'] ) ? $args['default'] : null,
  407. );
  408. $rest_args = array_merge( $default_args, $rest_args );
  409. $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
  410. $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
  411. $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
  412. if ( null === $rest_args['schema']['default'] ) {
  413. $rest_args['schema']['default'] = static::get_empty_value_for_type( $type );
  414. }
  415. $rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] );
  416. if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
  417. continue;
  418. }
  419. if ( empty( $rest_args['single'] ) ) {
  420. $rest_args['schema'] = array(
  421. 'type' => 'array',
  422. 'items' => $rest_args['schema'],
  423. );
  424. }
  425. $registered[ $name ] = $rest_args;
  426. }
  427. return $registered;
  428. }
  429. /**
  430. * Retrieves the object's meta schema, conforming to JSON Schema.
  431. *
  432. * @since 4.7.0
  433. *
  434. * @return array Field schema data.
  435. */
  436. public function get_field_schema() {
  437. $fields = $this->get_registered_fields();
  438. $schema = array(
  439. 'description' => __( 'Meta fields.' ),
  440. 'type' => 'object',
  441. 'context' => array( 'view', 'edit' ),
  442. 'properties' => array(),
  443. 'arg_options' => array(
  444. 'sanitize_callback' => null,
  445. 'validate_callback' => array( $this, 'check_meta_is_array' ),
  446. ),
  447. );
  448. foreach ( $fields as $args ) {
  449. $schema['properties'][ $args['name'] ] = $args['schema'];
  450. }
  451. return $schema;
  452. }
  453. /**
  454. * Prepares a meta value for output.
  455. *
  456. * Default preparation for meta fields. Override by passing the
  457. * `prepare_callback` in your `show_in_rest` options.
  458. *
  459. * @since 4.7.0
  460. *
  461. * @param mixed $value Meta value from the database.
  462. * @param WP_REST_Request $request Request object.
  463. * @param array $args REST-specific options for the meta key.
  464. * @return mixed Value prepared for output. If a non-JsonSerializable object, null.
  465. */
  466. public static function prepare_value( $value, $request, $args ) {
  467. if ( $args['single'] ) {
  468. $schema = $args['schema'];
  469. } else {
  470. $schema = $args['schema']['items'];
  471. }
  472. if ( '' === $value && in_array( $schema['type'], array( 'boolean', 'integer', 'number' ), true ) ) {
  473. $value = static::get_empty_value_for_type( $schema['type'] );
  474. }
  475. if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
  476. return null;
  477. }
  478. return rest_sanitize_value_from_schema( $value, $schema );
  479. }
  480. /**
  481. * Check the 'meta' value of a request is an associative array.
  482. *
  483. * @since 4.7.0
  484. *
  485. * @param mixed $value The meta value submitted in the request.
  486. * @param WP_REST_Request $request Full details about the request.
  487. * @param string $param The parameter name.
  488. * @return array|false The meta array, if valid, false otherwise.
  489. */
  490. public function check_meta_is_array( $value, $request, $param ) {
  491. if ( ! is_array( $value ) ) {
  492. return false;
  493. }
  494. return $value;
  495. }
  496. /**
  497. * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting
  498. * is specified.
  499. *
  500. * This is needed to restrict properties of objects in meta values to only
  501. * registered items, as the REST API will allow additional properties by
  502. * default.
  503. *
  504. * @since 5.3.0
  505. * @deprecated 5.6.0 Use rest_default_additional_properties_to_false() instead.
  506. *
  507. * @param array $schema The schema array.
  508. * @return array
  509. */
  510. protected function default_additional_properties_to_false( $schema ) {
  511. _deprecated_function( __METHOD__, '5.6.0', 'rest_default_additional_properties_to_false()' );
  512. return rest_default_additional_properties_to_false( $schema );
  513. }
  514. /**
  515. * Gets the empty value for a schema type.
  516. *
  517. * @since 5.3.0
  518. *
  519. * @param string $type The schema type.
  520. * @return mixed
  521. */
  522. protected static function get_empty_value_for_type( $type ) {
  523. switch ( $type ) {
  524. case 'string':
  525. return '';
  526. case 'boolean':
  527. return false;
  528. case 'integer':
  529. return 0;
  530. case 'number':
  531. return 0.0;
  532. case 'array':
  533. case 'object':
  534. return array();
  535. default:
  536. return null;
  537. }
  538. }
  539. }