abstract-wc-rest-controller.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <?php
  2. /**
  3. * REST Controller
  4. *
  5. * @class WC_REST_Controller
  6. * @package WooCommerce/Abstracts
  7. */
  8. if ( ! defined( 'ABSPATH' ) ) {
  9. exit;
  10. }
  11. /**
  12. * Abstract Rest Controller Class
  13. *
  14. * @package WooCommerce/Abstracts
  15. * @extends WP_REST_Controller
  16. * @version 2.6.0
  17. */
  18. abstract class WC_REST_Controller extends WP_REST_Controller {
  19. /**
  20. * Endpoint namespace.
  21. *
  22. * @var string
  23. */
  24. protected $namespace = 'wc/v1';
  25. /**
  26. * Route base.
  27. *
  28. * @var string
  29. */
  30. protected $rest_base = '';
  31. /**
  32. * Add the schema from additional fields to an schema array.
  33. *
  34. * The type of object is inferred from the passed schema.
  35. *
  36. * @param array $schema Schema array.
  37. *
  38. * @return array
  39. */
  40. protected function add_additional_fields_schema( $schema ) {
  41. if ( empty( $schema['title'] ) ) {
  42. return $schema;
  43. }
  44. /**
  45. * Can't use $this->get_object_type otherwise we cause an inf loop.
  46. */
  47. $object_type = $schema['title'];
  48. $additional_fields = $this->get_additional_fields( $object_type );
  49. foreach ( $additional_fields as $field_name => $field_options ) {
  50. if ( ! $field_options['schema'] ) {
  51. continue;
  52. }
  53. $schema['properties'][ $field_name ] = $field_options['schema'];
  54. }
  55. $schema['properties'] = apply_filters( 'woocommerce_rest_' . $object_type . '_schema', $schema['properties'] );
  56. return $schema;
  57. }
  58. /**
  59. * Get normalized rest base.
  60. *
  61. * @return string
  62. */
  63. protected function get_normalized_rest_base() {
  64. return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
  65. }
  66. /**
  67. * Check batch limit.
  68. *
  69. * @param array $items Request items.
  70. * @return bool|WP_Error
  71. */
  72. protected function check_batch_limit( $items ) {
  73. $limit = apply_filters( 'woocommerce_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
  74. $total = 0;
  75. if ( ! empty( $items['create'] ) ) {
  76. $total += count( $items['create'] );
  77. }
  78. if ( ! empty( $items['update'] ) ) {
  79. $total += count( $items['update'] );
  80. }
  81. if ( ! empty( $items['delete'] ) ) {
  82. $total += count( $items['delete'] );
  83. }
  84. if ( $total > $limit ) {
  85. /* translators: %s: items limit */
  86. return new WP_Error( 'woocommerce_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), array( 'status' => 413 ) );
  87. }
  88. return true;
  89. }
  90. /**
  91. * Bulk create, update and delete items.
  92. *
  93. * @param WP_REST_Request $request Full details about the request.
  94. * @return array Of WP_Error or WP_REST_Response.
  95. */
  96. public function batch_items( $request ) {
  97. /**
  98. * REST Server
  99. *
  100. * @var WP_REST_Server $wp_rest_server
  101. */
  102. global $wp_rest_server;
  103. // Get the request params.
  104. $items = array_filter( $request->get_params() );
  105. $response = array();
  106. // Check batch limit.
  107. $limit = $this->check_batch_limit( $items );
  108. if ( is_wp_error( $limit ) ) {
  109. return $limit;
  110. }
  111. if ( ! empty( $items['create'] ) ) {
  112. foreach ( $items['create'] as $item ) {
  113. $_item = new WP_REST_Request( 'POST' );
  114. // Default parameters.
  115. $defaults = array();
  116. $schema = $this->get_public_item_schema();
  117. foreach ( $schema['properties'] as $arg => $options ) {
  118. if ( isset( $options['default'] ) ) {
  119. $defaults[ $arg ] = $options['default'];
  120. }
  121. }
  122. $_item->set_default_params( $defaults );
  123. // Set request parameters.
  124. $_item->set_body_params( $item );
  125. $_response = $this->create_item( $_item );
  126. if ( is_wp_error( $_response ) ) {
  127. $response['create'][] = array(
  128. 'id' => 0,
  129. 'error' => array(
  130. 'code' => $_response->get_error_code(),
  131. 'message' => $_response->get_error_message(),
  132. 'data' => $_response->get_error_data(),
  133. ),
  134. );
  135. } else {
  136. $response['create'][] = $wp_rest_server->response_to_data( $_response, '' );
  137. }
  138. }
  139. }
  140. if ( ! empty( $items['update'] ) ) {
  141. foreach ( $items['update'] as $item ) {
  142. $_item = new WP_REST_Request( 'PUT' );
  143. $_item->set_body_params( $item );
  144. $_response = $this->update_item( $_item );
  145. if ( is_wp_error( $_response ) ) {
  146. $response['update'][] = array(
  147. 'id' => $item['id'],
  148. 'error' => array(
  149. 'code' => $_response->get_error_code(),
  150. 'message' => $_response->get_error_message(),
  151. 'data' => $_response->get_error_data(),
  152. ),
  153. );
  154. } else {
  155. $response['update'][] = $wp_rest_server->response_to_data( $_response, '' );
  156. }
  157. }
  158. }
  159. if ( ! empty( $items['delete'] ) ) {
  160. foreach ( $items['delete'] as $id ) {
  161. $id = (int) $id;
  162. if ( 0 === $id ) {
  163. continue;
  164. }
  165. $_item = new WP_REST_Request( 'DELETE' );
  166. $_item->set_query_params( array(
  167. 'id' => $id,
  168. 'force' => true,
  169. ) );
  170. $_response = $this->delete_item( $_item );
  171. if ( is_wp_error( $_response ) ) {
  172. $response['delete'][] = array(
  173. 'id' => $id,
  174. 'error' => array(
  175. 'code' => $_response->get_error_code(),
  176. 'message' => $_response->get_error_message(),
  177. 'data' => $_response->get_error_data(),
  178. ),
  179. );
  180. } else {
  181. $response['delete'][] = $wp_rest_server->response_to_data( $_response, '' );
  182. }
  183. }
  184. }
  185. return $response;
  186. }
  187. /**
  188. * Validate a text value for a text based setting.
  189. *
  190. * @since 3.0.0
  191. * @param string $value Value.
  192. * @param array $setting Setting.
  193. * @return string
  194. */
  195. public function validate_setting_text_field( $value, $setting ) {
  196. $value = is_null( $value ) ? '' : $value;
  197. return wp_kses_post( trim( stripslashes( $value ) ) );
  198. }
  199. /**
  200. * Validate select based settings.
  201. *
  202. * @since 3.0.0
  203. * @param string $value Value.
  204. * @param array $setting Setting.
  205. * @return string|WP_Error
  206. */
  207. public function validate_setting_select_field( $value, $setting ) {
  208. if ( array_key_exists( $value, $setting['options'] ) ) {
  209. return $value;
  210. } else {
  211. return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
  212. }
  213. }
  214. /**
  215. * Validate multiselect based settings.
  216. *
  217. * @since 3.0.0
  218. * @param array $values Values.
  219. * @param array $setting Setting.
  220. * @return array|WP_Error
  221. */
  222. public function validate_setting_multiselect_field( $values, $setting ) {
  223. if ( empty( $values ) ) {
  224. return array();
  225. }
  226. if ( ! is_array( $values ) ) {
  227. return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
  228. }
  229. $final_values = array();
  230. foreach ( $values as $value ) {
  231. if ( array_key_exists( $value, $setting['options'] ) ) {
  232. $final_values[] = $value;
  233. }
  234. }
  235. return $final_values;
  236. }
  237. /**
  238. * Validate image_width based settings.
  239. *
  240. * @since 3.0.0
  241. * @param array $values Values.
  242. * @param array $setting Setting.
  243. * @return string|WP_Error
  244. */
  245. public function validate_setting_image_width_field( $values, $setting ) {
  246. if ( ! is_array( $values ) ) {
  247. return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
  248. }
  249. $current = $setting['value'];
  250. if ( isset( $values['width'] ) ) {
  251. $current['width'] = intval( $values['width'] );
  252. }
  253. if ( isset( $values['height'] ) ) {
  254. $current['height'] = intval( $values['height'] );
  255. }
  256. if ( isset( $values['crop'] ) ) {
  257. $current['crop'] = (bool) $values['crop'];
  258. }
  259. return $current;
  260. }
  261. /**
  262. * Validate radio based settings.
  263. *
  264. * @since 3.0.0
  265. * @param string $value Value.
  266. * @param array $setting Setting.
  267. * @return string|WP_Error
  268. */
  269. public function validate_setting_radio_field( $value, $setting ) {
  270. return $this->validate_setting_select_field( $value, $setting );
  271. }
  272. /**
  273. * Validate checkbox based settings.
  274. *
  275. * @since 3.0.0
  276. * @param string $value Value.
  277. * @param array $setting Setting.
  278. * @return string|WP_Error
  279. */
  280. public function validate_setting_checkbox_field( $value, $setting ) {
  281. if ( in_array( $value, array( 'yes', 'no' ) ) ) {
  282. return $value;
  283. } elseif ( empty( $value ) ) {
  284. $value = isset( $setting['default'] ) ? $setting['default'] : 'no';
  285. return $value;
  286. } else {
  287. return new WP_Error( 'rest_setting_value_invalid', __( 'An invalid setting value was passed.', 'woocommerce' ), array( 'status' => 400 ) );
  288. }
  289. }
  290. /**
  291. * Validate textarea based settings.
  292. *
  293. * @since 3.0.0
  294. * @param string $value Value.
  295. * @param array $setting Setting.
  296. * @return string
  297. */
  298. public function validate_setting_textarea_field( $value, $setting ) {
  299. $value = is_null( $value ) ? '' : $value;
  300. return wp_kses( trim( stripslashes( $value ) ),
  301. array_merge(
  302. array(
  303. 'iframe' => array(
  304. 'src' => true,
  305. 'style' => true,
  306. 'id' => true,
  307. 'class' => true,
  308. ),
  309. ),
  310. wp_kses_allowed_html( 'post' )
  311. )
  312. );
  313. }
  314. /**
  315. * Add meta query.
  316. *
  317. * @since 3.0.0
  318. * @param array $args Query args.
  319. * @param array $meta_query Meta query.
  320. * @return array
  321. */
  322. protected function add_meta_query( $args, $meta_query ) {
  323. if ( ! empty( $args['meta_query'] ) ) {
  324. $args['meta_query'] = array();
  325. }
  326. $args['meta_query'][] = $meta_query;
  327. return $args['meta_query'];
  328. }
  329. /**
  330. * Get the batch schema, conforming to JSON Schema.
  331. *
  332. * @return array
  333. */
  334. public function get_public_batch_schema() {
  335. $schema = array(
  336. '$schema' => 'http://json-schema.org/draft-04/schema#',
  337. 'title' => 'batch',
  338. 'type' => 'object',
  339. 'properties' => array(
  340. 'create' => array(
  341. 'description' => __( 'List of created resources.', 'woocommerce' ),
  342. 'type' => 'array',
  343. 'context' => array( 'view', 'edit' ),
  344. 'items' => array(
  345. 'type' => 'object',
  346. ),
  347. ),
  348. 'update' => array(
  349. 'description' => __( 'List of updated resources.', 'woocommerce' ),
  350. 'type' => 'array',
  351. 'context' => array( 'view', 'edit' ),
  352. 'items' => array(
  353. 'type' => 'object',
  354. ),
  355. ),
  356. 'delete' => array(
  357. 'description' => __( 'List of delete resources.', 'woocommerce' ),
  358. 'type' => 'array',
  359. 'context' => array( 'view', 'edit' ),
  360. 'items' => array(
  361. 'type' => 'integer',
  362. ),
  363. ),
  364. ),
  365. );
  366. return $schema;
  367. }
  368. }