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  */
 12 
 13 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 14 
 15 class WC_API_Customers extends WC_API_Resource {
 16 
 17     /** @var string $base the route base */
 18     protected $base = '/customers';
 19 
 20     /** @var string $created_at_min for date filtering */
 21     private $created_at_min = null;
 22 
 23     /** @var string $created_at_max for date filtering */
 24     private $created_at_max = null;
 25 
 26     /**
 27      * Setup class, overridden to provide customer data to order response
 28      *
 29      * @since 2.1
 30      * @param WC_API_Server $server
 31      * @return WC_API_Customers
 32      */
 33     public function __construct( WC_API_Server $server ) {
 34 
 35         parent::__construct( $server );
 36 
 37         // add customer data to order responses
 38         add_filter( 'woocommerce_api_order_response', array( $this, 'add_customer_data' ), 10, 2 );
 39 
 40         // modify WP_User_Query to support created_at date filtering
 41         add_action( 'pre_user_query', array( $this, 'modify_user_query' ) );
 42     }
 43 
 44     /**
 45      * Register the routes for this class
 46      *
 47      * GET /customers
 48      * GET /customers/count
 49      * GET /customers/<id>
 50      * GET /customers/<id>/orders
 51      *
 52      * @since 2.1
 53      * @param array $routes
 54      * @return array
 55      */
 56     public function register_routes( $routes ) {
 57 
 58         # GET /customers
 59         $routes[ $this->base ] = array(
 60             array( array( $this, 'get_customers' ),     WC_API_SERVER::READABLE ),
 61         );
 62 
 63         # GET /customers/count
 64         $routes[ $this->base . '/count'] = array(
 65             array( array( $this, 'get_customers_count' ), WC_API_SERVER::READABLE ),
 66         );
 67 
 68         # GET /customers/<id>
 69         $routes[ $this->base . '/(?P<id>\d+)' ] = array(
 70             array( array( $this, 'get_customer' ),  WC_API_SERVER::READABLE ),
 71         );
 72 
 73         # GET /customers/<id>/orders
 74         $routes[ $this->base . '/(?P<id>\d+)/orders' ] = array(
 75             array( array( $this, 'get_customer_orders' ), WC_API_SERVER::READABLE ),
 76         );
 77 
 78         return $routes;
 79     }
 80 
 81     /**
 82      * Get all customers
 83      *
 84      * @since 2.1
 85      * @param array $fields
 86      * @param array $filter
 87      * @param int $page
 88      * @return array
 89      */
 90     public function get_customers( $fields = null, $filter = array(), $page = 1 ) {
 91 
 92         $filter['page'] = $page;
 93 
 94         $query = $this->query_customers( $filter );
 95 
 96         $customers = array();
 97 
 98         foreach( $query->get_results() as $user_id ) {
 99 
100             if ( ! $this->is_readable( $user_id ) )
101                 continue;
102 
103             $customers[] = current( $this->get_customer( $user_id, $fields ) );
104         }
105 
106         $this->server->add_pagination_headers( $query );
107 
108         return array( 'customers' => $customers );
109     }
110 
111     /**
112      * Get the customer for the given ID
113      *
114      * @since 2.1
115      * @param int $id the customer ID
116      * @param string $fields
117      * @return array
118      */
119     public function get_customer( $id, $fields = null ) {
120         global $wpdb;
121 
122         $id = $this->validate_request( $id, 'customer', 'read' );
123 
124         if ( is_wp_error( $id ) )
125             return $id;
126 
127         $customer = new WP_User( $id );
128 
129         // get info about user's last order
130         $last_order = $wpdb->get_row( "SELECT id, post_date_gmt
131                         FROM $wpdb->posts AS posts
132                         LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id
133                         WHERE meta.meta_key = '_customer_user'
134                         AND   meta.meta_value = {$customer->ID}
135                         AND   posts.post_type = 'shop_order'
136                         AND   posts.post_status = 'publish'
137                     " );
138 
139         $customer_data = array(
140             'id'               => $customer->ID,
141             'created_at'       => $this->server->format_datetime( $customer->user_registered ),
142             'email'            => $customer->user_email,
143             'first_name'       => $customer->first_name,
144             'last_name'        => $customer->last_name,
145             'username'         => $customer->user_login,
146             'last_order_id'    => is_object( $last_order ) ? $last_order->id : null,
147             'last_order_date'  => is_object( $last_order ) ? $this->server->format_datetime( $last_order->post_date_gmt ) : null,
148             'orders_count'     => (int) $customer->_order_count,
149             'total_spent'      => wc_format_decimal( $customer->_money_spent, 2 ),
150             'avatar_url'       => $this->get_avatar_url( $customer->customer_email ),
151             'billing_address'  => array(
152                 'first_name' => $customer->billing_first_name,
153                 'last_name'  => $customer->billing_last_name,
154                 'company'    => $customer->billing_company,
155                 'address_1'  => $customer->billing_address_1,
156                 'address_2'  => $customer->billing_address_2,
157                 'city'       => $customer->billing_city,
158                 'state'      => $customer->billing_state,
159                 'postcode'   => $customer->billing_postcode,
160                 'country'    => $customer->billing_country,
161                 'email'      => $customer->billing_email,
162                 'phone'      => $customer->billing_phone,
163             ),
164             'shipping_address' => array(
165                 'first_name' => $customer->shipping_first_name,
166                 'last_name'  => $customer->shipping_last_name,
167                 'company'    => $customer->shipping_company,
168                 'address_1'  => $customer->shipping_address_1,
169                 'address_2'  => $customer->shipping_address_2,
170                 'city'       => $customer->shipping_city,
171                 'state'      => $customer->shipping_state,
172                 'postcode'   => $customer->shipping_postcode,
173                 'country'    => $customer->shipping_country,
174             ),
175         );
176 
177         return array( 'customer' => apply_filters( 'woocommerce_api_customer_response', $customer_data, $customer, $fields, $this->server ) );
178     }
179 
180     /**
181      * Get the total number of customers
182      *
183      * @since 2.1
184      * @param array $filter
185      * @return array
186      */
187     public function get_customers_count( $filter = array() ) {
188 
189         $query = $this->query_customers( $filter );
190 
191         if ( ! current_user_can( 'list_users' ) )
192             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 ) );
193 
194         return array( 'count' => count( $query->get_results() ) );
195     }
196 
197 
198     /**
199      * Create a customer
200      *
201      * @TODO implement in 2.2 with woocommerce_create_new_customer()
202      * @param array $data
203      * @return array
204      */
205     public function create_customer( $data ) {
206 
207         if ( ! current_user_can( 'create_users' ) )
208             return new WP_Error( 'woocommerce_api_user_cannot_create_customer', __( 'You do not have permission to create this customer', 'woocommerce' ), array( 'status' => 401 ) );
209 
210         return array();
211     }
212 
213     /**
214      * Edit a customer
215      *
216      * @TODO implement in 2.2
217      * @param int $id the customer ID
218      * @param array $data
219      * @return array
220      */
221     public function edit_customer( $id, $data ) {
222 
223         $id = $this->validate_request( $id, 'customer', 'edit' );
224 
225         if ( ! is_wp_error( $id ) )
226             return $id;
227 
228         return $this->get_customer( $id );
229     }
230 
231     /**
232      * Delete a customer
233      *
234      * @TODO enable along with PUT/POST in 2.2
235      * @param int $id the customer ID
236      * @return array
237      */
238     public function delete_customer( $id ) {
239 
240         $id = $this->validate_request( $id, 'customer', 'delete' );
241 
242         if ( ! is_wp_error( $id ) )
243             return $id;
244 
245         return $this->delete( $id, 'customer' );
246     }
247 
248     /**
249      * Get the orders for a customer
250      *
251      * @since 2.1
252      * @param int $id the customer ID
253      * @param string $fields fields to include in response
254      * @return array
255      */
256     public function get_customer_orders( $id, $fields = null ) {
257         global $wpdb;
258 
259         $id = $this->validate_request( $id, 'customer', 'read' );
260 
261         if ( is_wp_error( $id ) )
262             return $id;
263 
264         $order_ids = $wpdb->get_col( $wpdb->prepare( "SELECT id
265                         FROM $wpdb->posts AS posts
266                         LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id
267                         WHERE meta.meta_key = '_customer_user'
268                         AND   meta.meta_value = '%s'
269                         AND   posts.post_type = 'shop_order'
270                         AND   posts.post_status = 'publish'
271                     ", $id ) );
272 
273         if ( empty( $order_ids ) )
274             return array( 'orders' => array() );
275 
276         $orders = array();
277 
278         foreach ( $order_ids as $order_id ) {
279             $orders[] = current( WC()->api->WC_API_Orders->get_order( $order_id, $fields ) );
280         }
281 
282         return array( 'orders' => apply_filters( 'woocommerce_api_customer_orders_response', $orders, $id, $fields, $order_ids, $this->server ) );
283     }
284 
285     /**
286      * Helper method to get customer user objects
287      *
288      * Note that WP_User_Query does not have built-in pagination so limit & offset are used to provide limited
289      * pagination support
290      *
291      * @since 2.1
292      * @param array $args request arguments for filtering query
293      * @return WP_User_Query
294      */
295     private function query_customers( $args = array() ) {
296 
297         // default users per page
298         $users_per_page = get_option( 'posts_per_page' );
299 
300         // set base query arguments
301         $query_args = array(
302             'fields'  => 'ID',
303             'role'    => 'customer',
304             'orderby' => 'registered',
305             'number'  => $users_per_page,
306         );
307 
308         // search
309         if ( ! empty( $args['q'] ) ) {
310             $query_args['search'] = $args['q'];
311         }
312 
313         // limit number of users returned
314         if ( ! empty( $args['limit'] ) ) {
315 
316             $query_args['number'] = absint( $args['limit'] );
317 
318             $users_per_page = absint( $args['limit'] );
319         }
320 
321         // page
322         $page = ( isset( $args['page'] ) ) ? absint( $args['page'] ) : 1;
323 
324         // offset
325         if ( ! empty( $args['offset'] ) ) {
326             $query_args['offset'] = absint( $args['offset'] );
327         } else {
328             $query_args['offset'] = $users_per_page * ( $page - 1 );
329         }
330 
331         // created date
332         if ( ! empty( $args['created_at_min'] ) ) {
333             $this->created_at_min = $this->server->parse_datetime( $args['created_at_min'] );
334         }
335 
336         if ( ! empty( $args['created_at_max'] ) ) {
337             $this->created_at_max = $this->server->parse_datetime( $args['created_at_max'] );
338         }
339 
340         $query = new WP_User_Query( $query_args );
341 
342         // helper members for pagination headers
343         $query->total_pages = ceil( $query->get_total() / $users_per_page );
344         $query->page = $page;
345 
346         return $query;
347     }
348 
349     /**
350      * Add customer data to orders
351      *
352      * @since 2.1
353      * @param $order_data
354      * @param $order
355      * @return array
356      */
357     public function add_customer_data( $order_data, $order ) {
358 
359         if ( 0 == $order->customer_user ) {
360 
361             // add customer data from order
362             $order_data['customer'] = array(
363                 'id'               => 0,
364                 'email'            => $order->billing_email,
365                 'first_name'       => $order->billing_first_name,
366                 'last_name'        => $order->billing_last_name,
367                 'billing_address'  => array(
368                     'first_name' => $order->billing_first_name,
369                     'last_name'  => $order->billing_last_name,
370                     'company'    => $order->billing_company,
371                     'address_1'  => $order->billing_address_1,
372                     'address_2'  => $order->billing_address_2,
373                     'city'       => $order->billing_city,
374                     'state'      => $order->billing_state,
375                     'postcode'   => $order->billing_postcode,
376                     'country'    => $order->billing_country,
377                     'email'      => $order->billing_email,
378                     'phone'      => $order->billing_phone,
379                 ),
380                 'shipping_address' => array(
381                     'first_name' => $order->shipping_first_name,
382                     'last_name'  => $order->shipping_last_name,
383                     'company'    => $order->shipping_company,
384                     'address_1'  => $order->shipping_address_1,
385                     'address_2'  => $order->shipping_address_2,
386                     'city'       => $order->shipping_city,
387                     'state'      => $order->shipping_state,
388                     'postcode'   => $order->shipping_postcode,
389                     'country'    => $order->shipping_country,
390                 ),
391             );
392 
393         } else {
394 
395             $order_data['customer'] = current( $this->get_customer( $order->customer_user ) );
396         }
397 
398         return $order_data;
399     }
400 
401     /**
402      * Modify the WP_User_Query to support filtering on the date the customer was created
403      *
404      * @since 2.1
405      * @param WP_User_Query $query
406      */
407     public function modify_user_query( $query ) {
408 
409         if ( $this->created_at_min )
410             $query->query_where .= sprintf( " AND user_registered >= STR_TO_DATE( '%s', '%%Y-%%m-%%d %%h:%%i:%%s' )", esc_sql( $this->created_at_min ) );
411 
412         if ( $this->created_at_max )
413             $query->query_where .= sprintf( " AND user_registered <= STR_TO_DATE( '%s', '%%Y-%%m-%%d %%h:%%i:%%s' )", esc_sql( $this->created_at_max ) );
414     }
415 
416     /**
417      * Wrapper for @see get_avatar() which doesn't simply return
418      * the URL so we need to pluck it from the HTML img tag
419      *
420      * @since 2.1
421      * @param string $email the customer's email
422      * @return string the URL to the customer's avatar
423      */
424     private function get_avatar_url( $email ) {
425 
426         $dom = new DOMDocument();
427 
428         $dom->loadHTML( get_avatar( $email ) );
429 
430         $url = $dom->getElementsByTagName( 'img' )->item( 0 )->getAttribute( 'src' );
431 
432         return ( ! empty( $url ) ) ? $url : null;
433     }
434 
435     /**
436      * Validate the request by checking:
437      *
438      * 1) the ID is a valid integer
439      * 2) the ID returns a valid WP_User
440      * 3) the current user has the proper permissions
441      *
442      * @since 2.1
443      * @see WC_API_Resource::validate_request()
444      * @param string|int $id the customer ID
445      * @param string $type the request type, unused because this method overrides the parent class
446      * @param string $context the context of the request, either `read`, `edit` or `delete`
447      * @return int|WP_Error valid user ID or WP_Error if any of the checks fails
448      */
449     protected function validate_request( $id, $type, $context ) {
450 
451         $id = absint( $id );
452 
453         // validate ID
454         if ( empty( $id ) )
455             return new WP_Error( 'woocommerce_api_invalid_customer_id', __( 'Invalid customer ID', 'woocommerce' ), array( 'status' => 404 ) );
456 
457         // non-existent IDs return a valid WP_User object with the user ID = 0
458         $customer = new WP_User( $id );
459 
460         if ( 0 === $customer->ID )
461             return new WP_Error( 'woocommerce_api_invalid_customer', __( 'Invalid customer', 'woocommerce' ), array( 'status' => 404 ) );
462 
463         // validate permissions
464         switch ( $context ) {
465 
466             case 'read':
467                 if ( ! current_user_can( 'list_users' ) )
468                     return new WP_Error( 'woocommerce_api_user_cannot_read_customer', __( 'You do not have permission to read this customer', 'woocommerce' ), array( 'status' => 401 ) );
469                 break;
470 
471             case 'edit':
472                 if ( ! current_user_can( 'edit_users' ) )
473                     return new WP_Error( 'woocommerce_api_user_cannot_edit_customer', __( 'You do not have permission to edit this customer', 'woocommerce' ), array( 'status' => 401 ) );
474                 break;
475 
476             case 'delete':
477                 if ( ! current_user_can( 'delete_users' ) )
478                     return new WP_Error( 'woocommerce_api_user_cannot_delete_customer', __( 'You do not have permission to delete this customer', 'woocommerce' ), array( 'status' => 401 ) );
479                 break;
480         }
481 
482         return $id;
483     }
484 
485     /**
486      * Check if the current user can read users
487      *
488      * @since 2.1
489      * @see WC_API_Resource::is_readable()
490      * @param int|WP_Post $post unused
491      * @return bool true if the current user can read users, false otherwise
492      */
493     protected function is_readable( $post ) {
494 
495         return current_user_can( 'list_users' );
496     }
497 
498 }
499 
WooCommerce API documentation generated by ApiGen 2.8.0