class-wc-api-customers.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <?php
  2. /**
  3. * WooCommerce API Customers Class
  4. *
  5. * Handles requests to the /customers endpoint
  6. *
  7. * @author WooThemes
  8. * @category API
  9. * @package WooCommerce/API
  10. * @since 2.1
  11. * @version 2.1
  12. */
  13. if ( ! defined( 'ABSPATH' ) ) {
  14. exit; // Exit if accessed directly
  15. }
  16. class WC_API_Customers extends WC_API_Resource {
  17. /** @var string $base the route base */
  18. protected $base = '/customers';
  19. /** @var string $created_at_min for date filtering */
  20. private $created_at_min = null;
  21. /** @var string $created_at_max for date filtering */
  22. private $created_at_max = null;
  23. /**
  24. * Setup class, overridden to provide customer data to order response
  25. *
  26. * @since 2.1
  27. * @param WC_API_Server $server
  28. */
  29. public function __construct( WC_API_Server $server ) {
  30. parent::__construct( $server );
  31. // add customer data to order responses
  32. add_filter( 'woocommerce_api_order_response', array( $this, 'add_customer_data' ), 10, 2 );
  33. // modify WP_User_Query to support created_at date filtering
  34. add_action( 'pre_user_query', array( $this, 'modify_user_query' ) );
  35. }
  36. /**
  37. * Register the routes for this class
  38. *
  39. * GET /customers
  40. * GET /customers/count
  41. * GET /customers/<id>
  42. * GET /customers/<id>/orders
  43. *
  44. * @since 2.1
  45. * @param array $routes
  46. * @return array
  47. */
  48. public function register_routes( $routes ) {
  49. # GET /customers
  50. $routes[ $this->base ] = array(
  51. array( array( $this, 'get_customers' ), WC_API_SERVER::READABLE ),
  52. );
  53. # GET /customers/count
  54. $routes[ $this->base . '/count' ] = array(
  55. array( array( $this, 'get_customers_count' ), WC_API_SERVER::READABLE ),
  56. );
  57. # GET /customers/<id>
  58. $routes[ $this->base . '/(?P<id>\d+)' ] = array(
  59. array( array( $this, 'get_customer' ), WC_API_SERVER::READABLE ),
  60. );
  61. # GET /customers/<id>/orders
  62. $routes[ $this->base . '/(?P<id>\d+)/orders' ] = array(
  63. array( array( $this, 'get_customer_orders' ), WC_API_SERVER::READABLE ),
  64. );
  65. return $routes;
  66. }
  67. /**
  68. * Get all customers
  69. *
  70. * @since 2.1
  71. * @param array $fields
  72. * @param array $filter
  73. * @param int $page
  74. * @return array
  75. */
  76. public function get_customers( $fields = null, $filter = array(), $page = 1 ) {
  77. $filter['page'] = $page;
  78. $query = $this->query_customers( $filter );
  79. $customers = array();
  80. foreach ( $query->get_results() as $user_id ) {
  81. if ( ! $this->is_readable( $user_id ) ) {
  82. continue;
  83. }
  84. $customers[] = current( $this->get_customer( $user_id, $fields ) );
  85. }
  86. $this->server->add_pagination_headers( $query );
  87. return array( 'customers' => $customers );
  88. }
  89. /**
  90. * Get the customer for the given ID
  91. *
  92. * @since 2.1
  93. * @param int $id the customer ID
  94. * @param string $fields
  95. * @return array|WP_Error
  96. */
  97. public function get_customer( $id, $fields = null ) {
  98. global $wpdb;
  99. $id = $this->validate_request( $id, 'customer', 'read' );
  100. if ( is_wp_error( $id ) ) {
  101. return $id;
  102. }
  103. $customer = new WC_Customer( $id );
  104. $last_order = $customer->get_last_order();
  105. $customer_data = array(
  106. 'id' => $customer->get_id(),
  107. 'created_at' => $this->server->format_datetime( $customer->get_date_created() ? $customer->get_date_created()->getTimestamp() : 0 ), // API gives UTC times.
  108. 'email' => $customer->get_email(),
  109. 'first_name' => $customer->get_first_name(),
  110. 'last_name' => $customer->get_last_name(),
  111. 'username' => $customer->get_username(),
  112. 'last_order_id' => is_object( $last_order ) ? $last_order->get_id() : null,
  113. 'last_order_date' => is_object( $last_order ) ? $this->server->format_datetime( $last_order->get_date_created() ? $last_order->get_date_created()->getTimestamp() : 0 ) : null, // API gives UTC times.
  114. 'orders_count' => $customer->get_order_count(),
  115. 'total_spent' => wc_format_decimal( $customer->get_total_spent(), 2 ),
  116. 'avatar_url' => $customer->get_avatar_url(),
  117. 'billing_address' => array(
  118. 'first_name' => $customer->get_billing_first_name(),
  119. 'last_name' => $customer->get_billing_last_name(),
  120. 'company' => $customer->get_billing_company(),
  121. 'address_1' => $customer->get_billing_address_1(),
  122. 'address_2' => $customer->get_billing_address_2(),
  123. 'city' => $customer->get_billing_city(),
  124. 'state' => $customer->get_billing_state(),
  125. 'postcode' => $customer->get_billing_postcode(),
  126. 'country' => $customer->get_billing_country(),
  127. 'email' => $customer->get_billing_email(),
  128. 'phone' => $customer->get_billing_phone(),
  129. ),
  130. 'shipping_address' => array(
  131. 'first_name' => $customer->get_shipping_first_name(),
  132. 'last_name' => $customer->get_shipping_last_name(),
  133. 'company' => $customer->get_shipping_company(),
  134. 'address_1' => $customer->get_shipping_address_1(),
  135. 'address_2' => $customer->get_shipping_address_2(),
  136. 'city' => $customer->get_shipping_city(),
  137. 'state' => $customer->get_shipping_state(),
  138. 'postcode' => $customer->get_shipping_postcode(),
  139. 'country' => $customer->get_shipping_country(),
  140. ),
  141. );
  142. return array( 'customer' => apply_filters( 'woocommerce_api_customer_response', $customer_data, $customer, $fields, $this->server ) );
  143. }
  144. /**
  145. * Get the total number of customers
  146. *
  147. * @since 2.1
  148. * @param array $filter
  149. * @return array|WP_Error
  150. */
  151. public function get_customers_count( $filter = array() ) {
  152. $query = $this->query_customers( $filter );
  153. if ( ! current_user_can( 'list_users' ) ) {
  154. return new WP_Error( 'woocommerce_api_user_cannot_read_customers_count', __( 'You do not have permission to read the customers count', 'woocommerce' ), array( 'status' => 401 ) );
  155. }
  156. return array( 'count' => count( $query->get_results() ) );
  157. }
  158. /**
  159. * Create a customer
  160. *
  161. * @param array $data
  162. * @return array|WP_Error
  163. */
  164. public function create_customer( $data ) {
  165. if ( ! current_user_can( 'create_users' ) ) {
  166. return new WP_Error( 'woocommerce_api_user_cannot_create_customer', __( 'You do not have permission to create this customer', 'woocommerce' ), array( 'status' => 401 ) );
  167. }
  168. return array();
  169. }
  170. /**
  171. * Edit a customer
  172. *
  173. * @param int $id the customer ID
  174. * @param array $data
  175. * @return array|WP_Error
  176. */
  177. public function edit_customer( $id, $data ) {
  178. $id = $this->validate_request( $id, 'customer', 'edit' );
  179. if ( ! is_wp_error( $id ) ) {
  180. return $id;
  181. }
  182. return $this->get_customer( $id );
  183. }
  184. /**
  185. * Delete a customer
  186. *
  187. * @param int $id the customer ID
  188. * @return array|WP_Error
  189. */
  190. public function delete_customer( $id ) {
  191. $id = $this->validate_request( $id, 'customer', 'delete' );
  192. if ( ! is_wp_error( $id ) ) {
  193. return $id;
  194. }
  195. return $this->delete( $id, 'customer' );
  196. }
  197. /**
  198. * Get the orders for a customer
  199. *
  200. * @since 2.1
  201. * @param int $id the customer ID
  202. * @param string $fields fields to include in response
  203. * @return array|WP_Error
  204. */
  205. public function get_customer_orders( $id, $fields = null ) {
  206. global $wpdb;
  207. $id = $this->validate_request( $id, 'customer', 'read' );
  208. if ( is_wp_error( $id ) ) {
  209. return $id;
  210. }
  211. $order_ids = wc_get_orders( array(
  212. 'customer' => $id,
  213. 'limit' => -1,
  214. 'orderby' => 'date',
  215. 'order' => 'ASC',
  216. 'return' => 'ids',
  217. ) );
  218. if ( empty( $order_ids ) ) {
  219. return array( 'orders' => array() );
  220. }
  221. $orders = array();
  222. foreach ( $order_ids as $order_id ) {
  223. $orders[] = current( WC()->api->WC_API_Orders->get_order( $order_id, $fields ) );
  224. }
  225. return array( 'orders' => apply_filters( 'woocommerce_api_customer_orders_response', $orders, $id, $fields, $order_ids, $this->server ) );
  226. }
  227. /**
  228. * Helper method to get customer user objects
  229. *
  230. * Note that WP_User_Query does not have built-in pagination so limit & offset are used to provide limited
  231. * pagination support
  232. *
  233. * @since 2.1
  234. * @param array $args request arguments for filtering query
  235. * @return WP_User_Query
  236. */
  237. private function query_customers( $args = array() ) {
  238. // default users per page
  239. $users_per_page = get_option( 'posts_per_page' );
  240. // set base query arguments
  241. $query_args = array(
  242. 'fields' => 'ID',
  243. 'role' => 'customer',
  244. 'orderby' => 'registered',
  245. 'number' => $users_per_page,
  246. );
  247. // search
  248. if ( ! empty( $args['q'] ) ) {
  249. $query_args['search'] = $args['q'];
  250. }
  251. // limit number of users returned
  252. if ( ! empty( $args['limit'] ) ) {
  253. $query_args['number'] = absint( $args['limit'] );
  254. $users_per_page = absint( $args['limit'] );
  255. }
  256. // page
  257. $page = ( isset( $args['page'] ) ) ? absint( $args['page'] ) : 1;
  258. // offset
  259. if ( ! empty( $args['offset'] ) ) {
  260. $query_args['offset'] = absint( $args['offset'] );
  261. } else {
  262. $query_args['offset'] = $users_per_page * ( $page - 1 );
  263. }
  264. // created date
  265. if ( ! empty( $args['created_at_min'] ) ) {
  266. $this->created_at_min = $this->server->parse_datetime( $args['created_at_min'] );
  267. }
  268. if ( ! empty( $args['created_at_max'] ) ) {
  269. $this->created_at_max = $this->server->parse_datetime( $args['created_at_max'] );
  270. }
  271. $query = new WP_User_Query( $query_args );
  272. // helper members for pagination headers
  273. $query->total_pages = ceil( $query->get_total() / $users_per_page );
  274. $query->page = $page;
  275. return $query;
  276. }
  277. /**
  278. * Add customer data to orders
  279. *
  280. * @since 2.1
  281. * @param $order_data
  282. * @param $order
  283. * @return array
  284. */
  285. public function add_customer_data( $order_data, $order ) {
  286. if ( 0 == $order->get_user_id() ) {
  287. // add customer data from order
  288. $order_data['customer'] = array(
  289. 'id' => 0,
  290. 'email' => $order->get_billing_email(),
  291. 'first_name' => $order->get_billing_first_name(),
  292. 'last_name' => $order->get_billing_last_name(),
  293. 'billing_address' => array(
  294. 'first_name' => $order->get_billing_first_name(),
  295. 'last_name' => $order->get_billing_last_name(),
  296. 'company' => $order->get_billing_company(),
  297. 'address_1' => $order->get_billing_address_1(),
  298. 'address_2' => $order->get_billing_address_2(),
  299. 'city' => $order->get_billing_city(),
  300. 'state' => $order->get_billing_state(),
  301. 'postcode' => $order->get_billing_postcode(),
  302. 'country' => $order->get_billing_country(),
  303. 'email' => $order->get_billing_email(),
  304. 'phone' => $order->get_billing_phone(),
  305. ),
  306. 'shipping_address' => array(
  307. 'first_name' => $order->get_shipping_first_name(),
  308. 'last_name' => $order->get_shipping_last_name(),
  309. 'company' => $order->get_shipping_company(),
  310. 'address_1' => $order->get_shipping_address_1(),
  311. 'address_2' => $order->get_shipping_address_2(),
  312. 'city' => $order->get_shipping_city(),
  313. 'state' => $order->get_shipping_state(),
  314. 'postcode' => $order->get_shipping_postcode(),
  315. 'country' => $order->get_shipping_country(),
  316. ),
  317. );
  318. } else {
  319. $order_data['customer'] = current( $this->get_customer( $order->get_user_id() ) );
  320. }
  321. return $order_data;
  322. }
  323. /**
  324. * Modify the WP_User_Query to support filtering on the date the customer was created
  325. *
  326. * @since 2.1
  327. * @param WP_User_Query $query
  328. */
  329. public function modify_user_query( $query ) {
  330. if ( $this->created_at_min ) {
  331. $query->query_where .= sprintf( " AND user_registered >= STR_TO_DATE( '%s', '%%Y-%%m-%%d %%h:%%i:%%s' )", esc_sql( $this->created_at_min ) );
  332. }
  333. if ( $this->created_at_max ) {
  334. $query->query_where .= sprintf( " AND user_registered <= STR_TO_DATE( '%s', '%%Y-%%m-%%d %%h:%%i:%%s' )", esc_sql( $this->created_at_max ) );
  335. }
  336. }
  337. /**
  338. * Validate the request by checking:
  339. *
  340. * 1) the ID is a valid integer
  341. * 2) the ID returns a valid WP_User
  342. * 3) the current user has the proper permissions
  343. *
  344. * @since 2.1
  345. * @see WC_API_Resource::validate_request()
  346. * @param string|int $id the customer ID
  347. * @param string $type the request type, unused because this method overrides the parent class
  348. * @param string $context the context of the request, either `read`, `edit` or `delete`
  349. * @return int|WP_Error valid user ID or WP_Error if any of the checks fails
  350. */
  351. protected function validate_request( $id, $type, $context ) {
  352. $id = absint( $id );
  353. // validate ID
  354. if ( empty( $id ) ) {
  355. return new WP_Error( 'woocommerce_api_invalid_customer_id', __( 'Invalid customer ID', 'woocommerce' ), array( 'status' => 404 ) );
  356. }
  357. // non-existent IDs return a valid WP_User object with the user ID = 0
  358. $customer = new WP_User( $id );
  359. if ( 0 === $customer->ID ) {
  360. return new WP_Error( 'woocommerce_api_invalid_customer', __( 'Invalid customer', 'woocommerce' ), array( 'status' => 404 ) );
  361. }
  362. // validate permissions
  363. switch ( $context ) {
  364. case 'read':
  365. if ( ! current_user_can( 'list_users' ) ) {
  366. return new WP_Error( 'woocommerce_api_user_cannot_read_customer', __( 'You do not have permission to read this customer', 'woocommerce' ), array( 'status' => 401 ) );
  367. }
  368. break;
  369. case 'edit':
  370. if ( ! current_user_can( 'edit_users' ) ) {
  371. return new WP_Error( 'woocommerce_api_user_cannot_edit_customer', __( 'You do not have permission to edit this customer', 'woocommerce' ), array( 'status' => 401 ) );
  372. }
  373. break;
  374. case 'delete':
  375. if ( ! current_user_can( 'delete_users' ) ) {
  376. return new WP_Error( 'woocommerce_api_user_cannot_delete_customer', __( 'You do not have permission to delete this customer', 'woocommerce' ), array( 'status' => 401 ) );
  377. }
  378. break;
  379. }
  380. return $id;
  381. }
  382. /**
  383. * Check if the current user can read users
  384. *
  385. * @since 2.1
  386. * @see WC_API_Resource::is_readable()
  387. * @param int|WP_Post $post unused
  388. * @return bool true if the current user can read users, false otherwise
  389. */
  390. protected function is_readable( $post ) {
  391. return current_user_can( 'list_users' );
  392. }
  393. }