Completed
Push — master ( a60b03...41517c )
by Claudio
27:03
created

WC_API_Customers::query_customers()   F

Complexity

Conditions 14
Paths 3456

Size

Total Lines 53
Code Lines 40

Duplication

Lines 11
Ratio 20.75 %
Metric Value
dl 11
loc 53
rs 3.2456
nc 3456
cc 14
eloc 40
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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