wc-rest-functions.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <?php
  2. /**
  3. * WooCommerce REST Functions
  4. *
  5. * Functions for REST specific things.
  6. *
  7. * @package WooCommerce/Functions
  8. * @version 2.6.0
  9. */
  10. defined( 'ABSPATH' ) || exit;
  11. /**
  12. * Parses and formats a date for ISO8601/RFC3339.
  13. *
  14. * Required WP 4.4 or later.
  15. * See https://developer.wordpress.org/reference/functions/mysql_to_rfc3339/
  16. *
  17. * @since 2.6.0
  18. * @param string|null|WC_DateTime $date Date.
  19. * @param bool $utc Send false to get local/offset time.
  20. * @return string|null ISO8601/RFC3339 formatted datetime.
  21. */
  22. function wc_rest_prepare_date_response( $date, $utc = true ) {
  23. if ( is_numeric( $date ) ) {
  24. $date = new WC_DateTime( "@$date", new DateTimeZone( 'UTC' ) );
  25. $date->setTimezone( new DateTimeZone( wc_timezone_string() ) );
  26. } elseif ( is_string( $date ) ) {
  27. $date = new WC_DateTime( $date, new DateTimeZone( 'UTC' ) );
  28. $date->setTimezone( new DateTimeZone( wc_timezone_string() ) );
  29. }
  30. if ( ! is_a( $date, 'WC_DateTime' ) ) {
  31. return null;
  32. }
  33. // Get timestamp before changing timezone to UTC.
  34. return gmdate( 'Y-m-d\TH:i:s', $utc ? $date->getTimestamp() : $date->getOffsetTimestamp() );
  35. }
  36. /**
  37. * Returns image mime types users are allowed to upload via the API.
  38. *
  39. * @since 2.6.4
  40. * @return array
  41. */
  42. function wc_rest_allowed_image_mime_types() {
  43. return apply_filters(
  44. 'woocommerce_rest_allowed_image_mime_types', array(
  45. 'jpg|jpeg|jpe' => 'image/jpeg',
  46. 'gif' => 'image/gif',
  47. 'png' => 'image/png',
  48. 'bmp' => 'image/bmp',
  49. 'tiff|tif' => 'image/tiff',
  50. 'ico' => 'image/x-icon',
  51. )
  52. );
  53. }
  54. /**
  55. * Upload image from URL.
  56. *
  57. * @since 2.6.0
  58. * @param string $image_url Image URL.
  59. * @return array|WP_Error Attachment data or error message.
  60. */
  61. function wc_rest_upload_image_from_url( $image_url ) {
  62. $file_name = basename( current( explode( '?', $image_url ) ) );
  63. $parsed_url = wp_parse_url( $image_url );
  64. // Check parsed URL.
  65. if ( ! $parsed_url || ! is_array( $parsed_url ) ) {
  66. /* translators: %s: image URL */
  67. return new WP_Error( 'woocommerce_rest_invalid_image_url', sprintf( __( 'Invalid URL %s.', 'woocommerce' ), $image_url ), array( 'status' => 400 ) );
  68. }
  69. // Ensure url is valid.
  70. $image_url = esc_url_raw( $image_url );
  71. // Get the file.
  72. $response = wp_safe_remote_get(
  73. $image_url, array(
  74. 'timeout' => 10,
  75. )
  76. );
  77. if ( is_wp_error( $response ) ) {
  78. return new WP_Error( 'woocommerce_rest_invalid_remote_image_url',
  79. /* translators: %s: image URL */
  80. sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ) . ' '
  81. /* translators: %s: error message */
  82. . sprintf( __( 'Error: %s.', 'woocommerce' ), $response->get_error_message() ), array( 'status' => 400 )
  83. );
  84. } elseif ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
  85. /* translators: %s: image URL */
  86. return new WP_Error( 'woocommerce_rest_invalid_remote_image_url', sprintf( __( 'Error getting remote image %s.', 'woocommerce' ), $image_url ), array( 'status' => 400 ) );
  87. }
  88. // Ensure we have a file name and type.
  89. $wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() );
  90. if ( ! $wp_filetype['type'] ) {
  91. $headers = wp_remote_retrieve_headers( $response );
  92. if ( isset( $headers['content-disposition'] ) && strstr( $headers['content-disposition'], 'filename=' ) ) {
  93. $content = explode( 'filename=', $headers['content-disposition'] );
  94. $disposition = end( $content );
  95. $disposition = sanitize_file_name( $disposition );
  96. $file_name = $disposition;
  97. } elseif ( isset( $headers['content-type'] ) && strstr( $headers['content-type'], 'image/' ) ) {
  98. $file_name = 'image.' . str_replace( 'image/', '', $headers['content-type'] );
  99. }
  100. unset( $headers );
  101. // Recheck filetype.
  102. $wp_filetype = wp_check_filetype( $file_name, wc_rest_allowed_image_mime_types() );
  103. if ( ! $wp_filetype['type'] ) {
  104. return new WP_Error( 'woocommerce_rest_invalid_image_type', __( 'Invalid image type.', 'woocommerce' ), array( 'status' => 400 ) );
  105. }
  106. }
  107. // Upload the file.
  108. $upload = wp_upload_bits( $file_name, '', wp_remote_retrieve_body( $response ) );
  109. if ( $upload['error'] ) {
  110. return new WP_Error( 'woocommerce_rest_image_upload_error', $upload['error'], array( 'status' => 400 ) );
  111. }
  112. // Get filesize.
  113. $filesize = filesize( $upload['file'] );
  114. if ( ! $filesize ) {
  115. @unlink( $upload['file'] ); // @codingStandardsIgnoreLine
  116. unset( $upload );
  117. return new WP_Error( 'woocommerce_rest_image_upload_file_error', __( 'Zero size file downloaded.', 'woocommerce' ), array( 'status' => 400 ) );
  118. }
  119. do_action( 'woocommerce_rest_api_uploaded_image_from_url', $upload, $image_url );
  120. return $upload;
  121. }
  122. /**
  123. * Set uploaded image as attachment.
  124. *
  125. * @since 2.6.0
  126. * @param array $upload Upload information from wp_upload_bits.
  127. * @param int $id Post ID. Default to 0.
  128. * @return int Attachment ID
  129. */
  130. function wc_rest_set_uploaded_image_as_attachment( $upload, $id = 0 ) {
  131. $info = wp_check_filetype( $upload['file'] );
  132. $title = '';
  133. $content = '';
  134. if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
  135. include_once ABSPATH . 'wp-admin/includes/image.php';
  136. }
  137. $image_meta = wp_read_image_metadata( $upload['file'] );
  138. if ( $image_meta ) {
  139. if ( trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
  140. $title = wc_clean( $image_meta['title'] );
  141. }
  142. if ( trim( $image_meta['caption'] ) ) {
  143. $content = wc_clean( $image_meta['caption'] );
  144. }
  145. }
  146. $attachment = array(
  147. 'post_mime_type' => $info['type'],
  148. 'guid' => $upload['url'],
  149. 'post_parent' => $id,
  150. 'post_title' => $title ? $title : basename( $upload['file'] ),
  151. 'post_content' => $content,
  152. );
  153. $attachment_id = wp_insert_attachment( $attachment, $upload['file'], $id );
  154. if ( ! is_wp_error( $attachment_id ) ) {
  155. wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $upload['file'] ) );
  156. }
  157. return $attachment_id;
  158. }
  159. /**
  160. * Validate reports request arguments.
  161. *
  162. * @since 2.6.0
  163. * @param mixed $value Value to valdate.
  164. * @param WP_REST_Request $request Request instance.
  165. * @param string $param Param to validate.
  166. * @return WP_Error|boolean
  167. */
  168. function wc_rest_validate_reports_request_arg( $value, $request, $param ) {
  169. $attributes = $request->get_attributes();
  170. if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
  171. return true;
  172. }
  173. $args = $attributes['args'][ $param ];
  174. if ( 'string' === $args['type'] && ! is_string( $value ) ) {
  175. /* translators: 1: param 2: type */
  176. return new WP_Error( 'woocommerce_rest_invalid_param', sprintf( __( '%1$s is not of type %2$s', 'woocommerce' ), $param, 'string' ) );
  177. }
  178. if ( 'date' === $args['format'] ) {
  179. $regex = '#^\d{4}-\d{2}-\d{2}$#';
  180. if ( ! preg_match( $regex, $value, $matches ) ) {
  181. return new WP_Error( 'woocommerce_rest_invalid_date', __( 'The date you provided is invalid.', 'woocommerce' ) );
  182. }
  183. }
  184. return true;
  185. }
  186. /**
  187. * Encodes a value according to RFC 3986.
  188. * Supports multidimensional arrays.
  189. *
  190. * @since 2.6.0
  191. * @param string|array $value The value to encode.
  192. * @return string|array Encoded values.
  193. */
  194. function wc_rest_urlencode_rfc3986( $value ) {
  195. if ( is_array( $value ) ) {
  196. return array_map( 'wc_rest_urlencode_rfc3986', $value );
  197. } else {
  198. return str_replace( array( '+', '%7E' ), array( ' ', '~' ), rawurlencode( $value ) );
  199. }
  200. }
  201. /**
  202. * Check permissions of posts on REST API.
  203. *
  204. * @since 2.6.0
  205. * @param string $post_type Post type.
  206. * @param string $context Request context.
  207. * @param int $object_id Post ID.
  208. * @return bool
  209. */
  210. function wc_rest_check_post_permissions( $post_type, $context = 'read', $object_id = 0 ) {
  211. $contexts = array(
  212. 'read' => 'read_private_posts',
  213. 'create' => 'publish_posts',
  214. 'edit' => 'edit_post',
  215. 'delete' => 'delete_post',
  216. 'batch' => 'edit_others_posts',
  217. );
  218. if ( 'revision' === $post_type ) {
  219. $permission = false;
  220. } else {
  221. $cap = $contexts[ $context ];
  222. $post_type_object = get_post_type_object( $post_type );
  223. $permission = current_user_can( $post_type_object->cap->$cap, $object_id );
  224. }
  225. return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, $post_type );
  226. }
  227. /**
  228. * Check permissions of users on REST API.
  229. *
  230. * @since 2.6.0
  231. * @param string $context Request context.
  232. * @param int $object_id Post ID.
  233. * @return bool
  234. */
  235. function wc_rest_check_user_permissions( $context = 'read', $object_id = 0 ) {
  236. $contexts = array(
  237. 'read' => 'list_users',
  238. 'create' => 'edit_users',
  239. 'edit' => 'edit_users',
  240. 'delete' => 'delete_users',
  241. 'batch' => 'edit_users',
  242. );
  243. $permission = current_user_can( $contexts[ $context ], $object_id );
  244. return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, 'user' );
  245. }
  246. /**
  247. * Check permissions of product terms on REST API.
  248. *
  249. * @since 2.6.0
  250. * @param string $taxonomy Taxonomy.
  251. * @param string $context Request context.
  252. * @param int $object_id Post ID.
  253. * @return bool
  254. */
  255. function wc_rest_check_product_term_permissions( $taxonomy, $context = 'read', $object_id = 0 ) {
  256. $contexts = array(
  257. 'read' => 'manage_terms',
  258. 'create' => 'edit_terms',
  259. 'edit' => 'edit_terms',
  260. 'delete' => 'delete_terms',
  261. 'batch' => 'edit_terms',
  262. );
  263. $cap = $contexts[ $context ];
  264. $taxonomy_object = get_taxonomy( $taxonomy );
  265. $permission = current_user_can( $taxonomy_object->cap->$cap, $object_id );
  266. return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, $object_id, $taxonomy );
  267. }
  268. /**
  269. * Check manager permissions on REST API.
  270. *
  271. * @since 2.6.0
  272. * @param string $object Object.
  273. * @param string $context Request context.
  274. * @return bool
  275. */
  276. function wc_rest_check_manager_permissions( $object, $context = 'read' ) {
  277. $objects = array(
  278. 'reports' => 'view_woocommerce_reports',
  279. 'settings' => 'manage_woocommerce',
  280. 'system_status' => 'manage_woocommerce',
  281. 'attributes' => 'manage_product_terms',
  282. 'shipping_methods' => 'manage_woocommerce',
  283. 'payment_gateways' => 'manage_woocommerce',
  284. 'webhooks' => 'manage_woocommerce',
  285. );
  286. $permission = current_user_can( $objects[ $object ] );
  287. return apply_filters( 'woocommerce_rest_check_permissions', $permission, $context, 0, $object );
  288. }