Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

class-wp-rest-settings-controller.php 9.1 KiB

3 роки тому
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?php
  2. /**
  3. * REST API: WP_REST_Settings_Controller class
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 4.7.0
  8. */
  9. /**
  10. * Core class used to manage a site's settings via the REST API.
  11. *
  12. * @since 4.7.0
  13. *
  14. * @see WP_REST_Controller
  15. */
  16. class WP_REST_Settings_Controller extends WP_REST_Controller {
  17. /**
  18. * Constructor.
  19. *
  20. * @since 4.7.0
  21. * @access public
  22. */
  23. public function __construct() {
  24. $this->namespace = 'wp/v2';
  25. $this->rest_base = 'settings';
  26. }
  27. /**
  28. * Registers the routes for the objects of the controller.
  29. *
  30. * @since 4.7.0
  31. * @access public
  32. *
  33. * @see register_rest_route()
  34. */
  35. public function register_routes() {
  36. register_rest_route( $this->namespace, '/' . $this->rest_base, array(
  37. array(
  38. 'methods' => WP_REST_Server::READABLE,
  39. 'callback' => array( $this, 'get_item' ),
  40. 'args' => array(),
  41. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  42. ),
  43. array(
  44. 'methods' => WP_REST_Server::EDITABLE,
  45. 'callback' => array( $this, 'update_item' ),
  46. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  47. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  48. ),
  49. 'schema' => array( $this, 'get_public_item_schema' ),
  50. ) );
  51. }
  52. /**
  53. * Checks if a given request has access to read and manage settings.
  54. *
  55. * @since 4.7.0
  56. * @access public
  57. *
  58. * @param WP_REST_Request $request Full details about the request.
  59. * @return bool True if the request has read access for the item, otherwise false.
  60. */
  61. public function get_item_permissions_check( $request ) {
  62. return current_user_can( 'manage_options' );
  63. }
  64. /**
  65. * Retrieves the settings.
  66. *
  67. * @since 4.7.0
  68. * @access public
  69. *
  70. * @param WP_REST_Request $request Full details about the request.
  71. * @return array|WP_Error Array on success, or WP_Error object on failure.
  72. */
  73. public function get_item( $request ) {
  74. $options = $this->get_registered_options();
  75. $response = array();
  76. foreach ( $options as $name => $args ) {
  77. /**
  78. * Filters the value of a setting recognized by the REST API.
  79. *
  80. * Allow hijacking the setting value and overriding the built-in behavior by returning a
  81. * non-null value. The returned value will be presented as the setting value instead.
  82. *
  83. * @since 4.7.0
  84. *
  85. * @param mixed $result Value to use for the requested setting. Can be a scalar
  86. * matching the registered schema for the setting, or null to
  87. * follow the default get_option() behavior.
  88. * @param string $name Setting name (as shown in REST API responses).
  89. * @param array $args Arguments passed to register_setting() for this setting.
  90. */
  91. $response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args );
  92. if ( is_null( $response[ $name ] ) ) {
  93. // Default to a null value as "null" in the response means "not set".
  94. $response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] );
  95. }
  96. /*
  97. * Because get_option() is lossy, we have to
  98. * cast values to the type they are registered with.
  99. */
  100. $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
  101. }
  102. return $response;
  103. }
  104. /**
  105. * Prepares a value for output based off a schema array.
  106. *
  107. * @since 4.7.0
  108. * @access protected
  109. *
  110. * @param mixed $value Value to prepare.
  111. * @param array $schema Schema to match.
  112. * @return mixed The prepared value.
  113. */
  114. protected function prepare_value( $value, $schema ) {
  115. // If the value is not a scalar, it's not possible to cast it to anything.
  116. if ( ! is_scalar( $value ) ) {
  117. return null;
  118. }
  119. switch ( $schema['type'] ) {
  120. case 'string':
  121. return (string) $value;
  122. case 'integer':
  123. return (int) $value;
  124. case 'number':
  125. return (float) $value;
  126. case 'boolean':
  127. return (bool) $value;
  128. default:
  129. return null;
  130. }
  131. }
  132. /**
  133. * Updates settings for the settings object.
  134. *
  135. * @since 4.7.0
  136. * @access public
  137. *
  138. * @param WP_REST_Request $request Full details about the request.
  139. * @return array|WP_Error Array on success, or error object on failure.
  140. */
  141. public function update_item( $request ) {
  142. $options = $this->get_registered_options();
  143. $params = $request->get_params();
  144. foreach ( $options as $name => $args ) {
  145. if ( ! array_key_exists( $name, $params ) ) {
  146. continue;
  147. }
  148. /**
  149. * Filters whether to preempt a setting value update.
  150. *
  151. * Allows hijacking the setting update logic and overriding the built-in behavior by
  152. * returning true.
  153. *
  154. * @since 4.7.0
  155. *
  156. * @param bool $result Whether to override the default behavior for updating the
  157. * value of a setting.
  158. * @param string $name Setting name (as shown in REST API responses).
  159. * @param mixed $value Updated setting value.
  160. * @param array $args Arguments passed to register_setting() for this setting.
  161. */
  162. $updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args );
  163. if ( $updated ) {
  164. continue;
  165. }
  166. /*
  167. * A null value for an option would have the same effect as
  168. * deleting the option from the database, and relying on the
  169. * default value.
  170. */
  171. if ( is_null( $request[ $name ] ) ) {
  172. /*
  173. * A null value is returned in the response for any option
  174. * that has a non-scalar value.
  175. *
  176. * To protect clients from accidentally including the null
  177. * values from a response object in a request, we do not allow
  178. * options with non-scalar values to be updated to null.
  179. * Without this added protection a client could mistakenly
  180. * delete all options that have non-scalar values from the
  181. * database.
  182. */
  183. if ( ! is_scalar( get_option( $args['option_name'], false ) ) ) {
  184. return new WP_Error(
  185. 'rest_invalid_stored_value', sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 )
  186. );
  187. }
  188. delete_option( $args['option_name'] );
  189. } else {
  190. update_option( $args['option_name'], $request[ $name ] );
  191. }
  192. }
  193. return $this->get_item( $request );
  194. }
  195. /**
  196. * Retrieves all of the registered options for the Settings API.
  197. *
  198. * @since 4.7.0
  199. * @access protected
  200. *
  201. * @return array Array of registered options.
  202. */
  203. protected function get_registered_options() {
  204. $rest_options = array();
  205. foreach ( get_registered_settings() as $name => $args ) {
  206. if ( empty( $args['show_in_rest'] ) ) {
  207. continue;
  208. }
  209. $rest_args = array();
  210. if ( is_array( $args['show_in_rest'] ) ) {
  211. $rest_args = $args['show_in_rest'];
  212. }
  213. $defaults = array(
  214. 'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name,
  215. 'schema' => array(),
  216. );
  217. $rest_args = array_merge( $defaults, $rest_args );
  218. $default_schema = array(
  219. 'type' => empty( $args['type'] ) ? null : $args['type'],
  220. 'description' => empty( $args['description'] ) ? '' : $args['description'],
  221. 'default' => isset( $args['default'] ) ? $args['default'] : null,
  222. );
  223. $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
  224. $rest_args['option_name'] = $name;
  225. // Skip over settings that don't have a defined type in the schema.
  226. if ( empty( $rest_args['schema']['type'] ) ) {
  227. continue;
  228. }
  229. /*
  230. * Whitelist the supported types for settings, as we don't want invalid types
  231. * to be updated with arbitrary values that we can't do decent sanitizing for.
  232. */
  233. if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean' ), true ) ) {
  234. continue;
  235. }
  236. $rest_options[ $rest_args['name'] ] = $rest_args;
  237. }
  238. return $rest_options;
  239. }
  240. /**
  241. * Retrieves the site setting schema, conforming to JSON Schema.
  242. *
  243. * @since 4.7.0
  244. * @access public
  245. *
  246. * @return array Item schema data.
  247. */
  248. public function get_item_schema() {
  249. $options = $this->get_registered_options();
  250. $schema = array(
  251. '$schema' => 'http://json-schema.org/schema#',
  252. 'title' => 'settings',
  253. 'type' => 'object',
  254. 'properties' => array(),
  255. );
  256. foreach ( $options as $option_name => $option ) {
  257. $schema['properties'][ $option_name ] = $option['schema'];
  258. $schema['properties'][ $option_name ]['arg_options'] = array(
  259. 'sanitize_callback' => array( $this, 'sanitize_callback' ),
  260. );
  261. }
  262. return $this->add_additional_fields_schema( $schema );
  263. }
  264. /**
  265. * Custom sanitize callback used for all options to allow the use of 'null'.
  266. *
  267. * By default, the schema of settings will throw an error if a value is set to
  268. * `null` as it's not a valid value for something like "type => string". We
  269. * provide a wrapper sanitizer to whitelist the use of `null`.
  270. *
  271. * @param mixed $value The value for the setting.
  272. * @param WP_REST_Request $request The request object.
  273. * @param string $param The parameter name.
  274. * @return mixed|WP_Error
  275. */
  276. public function sanitize_callback( $value, $request, $param ) {
  277. if ( is_null( $value ) ) {
  278. return $value;
  279. }
  280. return rest_parse_request_arg( $value, $request, $param );
  281. }
  282. }