WC_REST_Customers_Controller   C
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 871
Duplicated Lines 13.32 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 116
loc 871
rs 5
c 0
b 0
f 0
wmc 67
lcom 1
cbo 2

18 Methods

Rating   Name   Duplication   Size   Complexity  
A get_items_permissions_check() 7 7 2
A create_item_permissions_check() 7 7 2
A get_item_permissions_check() 9 9 2
A update_item_permissions_check() 9 9 2
A delete_item_permissions_check() 9 9 2
F get_items() 18 90 10
B create_item() 0 41 5
A get_item() 13 13 3
D update_customer_meta_fields() 14 31 9
A prepare_links() 12 12 1
A get_role_names() 0 5 1
A get_collection_params() 0 59 1
A batch_items_permissions_check() 7 7 2
A register_routes() 0 67 1
B prepare_item_for_response() 0 62 5
B get_item_schema() 0 217 1
D update_item() 6 45 9
D delete_item() 5 44 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_REST_Customers_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_REST_Customers_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * REST API Customers controller
4
 *
5
 * Handles requests to the /customers endpoint.
6
 *
7
 * @author   WooThemes
8
 * @category API
9
 * @package  WooCommerce/API
10
 * @since    2.6.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * REST API Customers controller class.
19
 *
20
 * @package WooCommerce/API
21
 * @extends WC_REST_Controller
22
 */
23
class WC_REST_Customers_Controller extends WC_REST_Controller {
24
25
	/**
26
	 * Endpoint namespace.
27
	 *
28
	 * @var string
29
	 */
30
	protected $namespace = 'wc/v1';
31
32
	/**
33
	 * Route base.
34
	 *
35
	 * @var string
36
	 */
37
	protected $rest_base = 'customers';
38
39
	/**
40
	 * Register the routes for customers.
41
	 */
42
	public function register_routes() {
43
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
44
			array(
45
				'methods'             => WP_REST_Server::READABLE,
46
				'callback'            => array( $this, 'get_items' ),
47
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
48
				'args'                => $this->get_collection_params(),
49
			),
50
			array(
51
				'methods'             => WP_REST_Server::CREATABLE,
52
				'callback'            => array( $this, 'create_item' ),
53
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
54
				'args'                => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
55
					'email' => array(
56
						'required' => true,
57
					),
58
					'username' => array(
59
						'required' => 'no' === get_option( 'woocommerce_registration_generate_username', 'yes' ),
60
					),
61
					'password' => array(
62
						'required' => 'no' === get_option( 'woocommerce_registration_generate_password', 'no' ),
63
					),
64
				) ),
65
			),
66
			'schema' => array( $this, 'get_public_item_schema' ),
67
		) );
68
69
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
70
			array(
71
				'methods'             => WP_REST_Server::READABLE,
72
				'callback'            => array( $this, 'get_item' ),
73
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
74
				'args'                => array(
75
					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
76
				),
77
			),
78
			array(
79
				'methods'             => WP_REST_Server::EDITABLE,
80
				'callback'            => array( $this, 'update_item' ),
81
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
82
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
83
			),
84
			array(
85
				'methods'             => WP_REST_Server::DELETABLE,
86
				'callback'            => array( $this, 'delete_item' ),
87
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
88
				'args'                => array(
89
					'force' => array(
90
						'default'     => false,
91
						'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
92
					),
93
					'reassign' => array(),
94
				),
95
			),
96
			'schema' => array( $this, 'get_public_item_schema' ),
97
		) );
98
99
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
100
			array(
101
				'methods'             => WP_REST_Server::EDITABLE,
102
				'callback'            => array( $this, 'batch_items' ),
103
				'permission_callback' => array( $this, 'batch_items_permissions_check' ),
104
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
105
			),
106
			'schema' => array( $this, 'get_public_batch_schema' ),
107
		) );
108
	}
109
110
	/**
111
	 * Check whether a given request has permission to read customers.
112
	 *
113
	 * @param  WP_REST_Request $request Full details about the request.
114
	 * @return WP_Error|boolean
115
	 */
116 View Code Duplication
	public function get_items_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
117
		if ( ! wc_rest_check_user_permissions( 'read' ) ) {
118
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
119
		}
120
121
		return true;
122
	}
123
124
	/**
125
	 * Check if a given request has access create customers.
126
	 *
127
	 * @param  WP_REST_Request $request Full details about the request.
128
	 * @return boolean
129
	 */
130 View Code Duplication
	public function create_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
		if ( ! wc_rest_check_user_permissions( 'create' ) ) {
132
			return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
133
		}
134
135
		return true;
136
	}
137
138
	/**
139
	 * Check if a given request has access to read a customer.
140
	 *
141
	 * @param  WP_REST_Request $request Full details about the request.
142
	 * @return WP_Error|boolean
143
	 */
144 View Code Duplication
	public function get_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
		$id = (int) $request['id'];
146
147
		if ( ! wc_rest_check_user_permissions( 'read', $id ) ) {
148
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
149
		}
150
151
		return true;
152
	}
153
154
	/**
155
	 * Check if a given request has access update a customer.
156
	 *
157
	 * @param  WP_REST_Request $request Full details about the request.
158
	 * @return boolean
159
	 */
160 View Code Duplication
	public function update_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
		$id = (int) $request['id'];
162
163
		if ( ! wc_rest_check_user_permissions( 'edit', $id ) ) {
164
			return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
165
		}
166
167
		return true;
168
	}
169
170
	/**
171
	 * Check if a given request has access delete a customer.
172
	 *
173
	 * @param  WP_REST_Request $request Full details about the request.
174
	 * @return boolean
175
	 */
176 View Code Duplication
	public function delete_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
		$id = (int) $request['id'];
178
179
		if ( ! wc_rest_check_user_permissions( 'delete', $id ) ) {
180
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
181
		}
182
183
		return true;
184
	}
185
186
	/**
187
	 * Check if a given request has access batch create, update and delete items.
188
	 *
189
	 * @param  WP_REST_Request $request Full details about the request.
190
	 * @return boolean
191
	 */
192 View Code Duplication
	public function batch_items_permissions_check( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
		if ( ! wc_rest_check_user_permissions( 'batch' ) ) {
194
			return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to manipule this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
195
		}
196
197
		return true;
198
	}
199
200
	/**
201
	 * Get all customers.
202
	 *
203
	 * @param WP_REST_Request $request Full details about the request.
204
	 * @return WP_Error|WP_REST_Response
205
	 */
206
	public function get_items( $request ) {
207
		$prepared_args = array();
208
		$prepared_args['exclude'] = $request['exclude'];
209
		$prepared_args['include'] = $request['include'];
210
		$prepared_args['order']   = $request['order'];
211
		$prepared_args['number']  = $request['per_page'];
212 View Code Duplication
		if ( ! empty( $request['offset'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
213
			$prepared_args['offset'] = $request['offset'];
214
		} else {
215
			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
216
		}
217
		$orderby_possibles = array(
218
			'id'              => 'ID',
219
			'include'         => 'include',
220
			'name'            => 'display_name',
221
			'registered_date' => 'registered',
222
		);
223
		$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
224
		$prepared_args['search']  = $request['search'];
225
226
		if ( '' !== $prepared_args['search'] ) {
227
			$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
228
		}
229
230
		// Filter by email.
231
		if ( ! empty( $request['email'] ) ) {
232
			$prepared_args['search']         = $request['email'];
233
			$prepared_args['search_columns'] = array( 'user_email' );
234
		}
235
236
		// Filter by role.
237
		if ( 'all' !== $request['role'] ) {
238
			$prepared_args['role'] = $request['role'];
239
		}
240
241
		/**
242
		 * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
243
		 *
244
		 * @see https://developer.wordpress.org/reference/classes/wp_user_query/
245
		 *
246
		 * @param array           $prepared_args Array of arguments for WP_User_Query.
247
		 * @param WP_REST_Request $request       The current request.
248
		 */
249
		$prepared_args = apply_filters( 'woocommerce_rest_customer_query', $prepared_args, $request );
250
251
		$query = new WP_User_Query( $prepared_args );
252
253
		$users = array();
254
		foreach ( $query->results as $user ) {
255
			$data = $this->prepare_item_for_response( $user, $request );
256
			$users[] = $this->prepare_response_for_collection( $data );
257
		}
258
259
		$response = rest_ensure_response( $users );
260
261
		// Store pagation values for headers then unset for count query.
262
		$per_page = (int) $prepared_args['number'];
263
		$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
264
265
		$prepared_args['fields'] = 'ID';
266
267
		$total_users = $query->get_total();
268
		if ( $total_users < 1 ) {
269
			// Out-of-bounds, run the query again without LIMIT for total count.
270
			unset( $prepared_args['number'] );
271
			unset( $prepared_args['offset'] );
272
			$count_query = new WP_User_Query( $prepared_args );
273
			$total_users = $count_query->get_total();
274
		}
275
		$response->header( 'X-WP-Total', (int) $total_users );
276
		$max_pages = ceil( $total_users / $per_page );
277
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
278
279
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
280 View Code Duplication
		if ( $page > 1 ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
281
			$prev_page = $page - 1;
282
			if ( $prev_page > $max_pages ) {
283
				$prev_page = $max_pages;
284
			}
285
			$prev_link = add_query_arg( 'page', $prev_page, $base );
286
			$response->link_header( 'prev', $prev_link );
287
		}
288 View Code Duplication
		if ( $max_pages > $page ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
289
			$next_page = $page + 1;
290
			$next_link = add_query_arg( 'page', $next_page, $base );
291
			$response->link_header( 'next', $next_link );
292
		}
293
294
		return $response;
295
	}
296
297
	/**
298
	 * Create a single customer.
299
	 *
300
	 * @param WP_REST_Request $request Full details about the request.
301
	 * @return WP_Error|WP_REST_Response
302
	 */
303
	public function create_item( $request ) {
304
		if ( ! empty( $request['id'] ) ) {
305
			return new WP_Error( 'woocommerce_rest_customer_exists', __( 'Cannot create existing resource.', 'woocommerce' ), array( 'status' => 400 ) );
306
		}
307
308
		// Sets the username.
309
		$request['username'] = ! empty( $request['username'] ) ? $request['username'] : '';
310
311
		// Sets the password.
312
		$request['password'] = ! empty( $request['password'] ) ? $request['password'] : '';
313
314
		// Create customer.
315
		$customer_id = wc_create_new_customer( $request['email'], $request['username'], $request['password'] );;
316
		if ( is_wp_error( $customer_id ) ) {
317
			return $customer_id;
318
		}
319
320
		$customer = get_user_by( 'id', $customer_id );
321
322
		$this->update_additional_fields_for_object( $customer, $request );
323
324
		// Add customer data.
325
		$this->update_customer_meta_fields( $customer, $request );
326
327
		/**
328
		 * Fires after a customer is created or updated via the REST API.
329
		 *
330
		 * @param WP_User         $customer  Data used to create the customer.
331
		 * @param WP_REST_Request $request   Request object.
332
		 * @param boolean         $creating  True when creating customer, false when updating customer.
333
		 */
334
		do_action( 'woocommerce_rest_insert_customer', $customer, $request, true );
335
336
		$request->set_param( 'context', 'edit' );
337
		$response = $this->prepare_item_for_response( $customer, $request );
338
		$response = rest_ensure_response( $response );
339
		$response->set_status( 201 );
340
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer_id ) ) );
341
342
		return $response;
343
	}
344
345
	/**
346
	 * Get a single customer.
347
	 *
348
	 * @param WP_REST_Request $request Full details about the request.
349
	 * @return WP_Error|WP_REST_Response
350
	 */
351 View Code Duplication
	public function get_item( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
		$id       = (int) $request['id'];
353
		$customer = get_userdata( $id );
354
355
		if ( empty( $id ) || empty( $customer->ID ) ) {
356
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 404 ) );
357
		}
358
359
		$customer = $this->prepare_item_for_response( $customer, $request );
360
		$response = rest_ensure_response( $customer );
361
362
		return $response;
363
	}
364
365
	/**
366
	 * Update a single user.
367
	 *
368
	 * @param WP_REST_Request $request Full details about the request.
369
	 * @return WP_Error|WP_REST_Response
370
	 */
371
	public function update_item( $request ) {
372
		$id       = (int) $request['id'];
373
		$customer = get_userdata( $id );
374
375
		if ( ! $customer ) {
376
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
377
		}
378
379
		if ( ! empty( $request['email'] ) && email_exists( $request['email'] ) && $request['email'] !== $customer->user_email ) {
380
			return new WP_Error( 'woocommerce_rest_customer_invalid_email', __( 'Email address is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
381
		}
382
383
		if ( ! empty( $request['username'] ) && $request['username'] !== $customer->user_login ) {
384
			return new WP_Error( 'woocommerce_rest_customer_invalid_argument', __( "Username isn't editable.", 'woocommerce' ), array( 'status' => 400 ) );
385
		}
386
387
		// Customer email.
388 View Code Duplication
		if ( isset( $request['email'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
389
			wp_update_user( array( 'ID' => $customer->ID, 'user_email' => sanitize_email( $request['email'] ) ) );
390
		}
391
392
		// Customer password.
393 View Code Duplication
		if ( isset( $request['password'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394
			wp_update_user( array( 'ID' => $customer->ID, 'user_pass' => wc_clean( $request['password'] ) ) );
395
		}
396
397
		$this->update_additional_fields_for_object( $customer, $request );
398
399
		// Update customer data.
400
		$this->update_customer_meta_fields( $customer, $request );
401
402
		/**
403
		 * Fires after a customer is created or updated via the REST API.
404
		 *
405
		 * @param WP_User         $customer  Data used to create the customer.
406
		 * @param WP_REST_Request $request   Request object.
407
		 * @param boolean         $creating  True when creating customer, false when updating customer.
408
		 */
409
		do_action( 'woocommerce_rest_insert_customer', $customer, $request, false );
410
411
		$request->set_param( 'context', 'edit' );
412
		$response = $this->prepare_item_for_response( $customer, $request );
413
		$response = rest_ensure_response( $response );
414
		return $response;
415
	}
416
417
	/**
418
	 * Delete a single customer.
419
	 *
420
	 * @param WP_REST_Request $request Full details about the request.
421
	 * @return WP_Error|WP_REST_Response
422
	 */
423
	public function delete_item( $request ) {
424
		$id       = (int) $request['id'];
425
		$reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
426
		$force    = isset( $request['force'] ) ? (bool) $request['force'] : false;
427
428
		// We don't support trashing for this type, error out.
429
		if ( ! $force ) {
430
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Customers do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
431
		}
432
433
		$customer = get_userdata( $id );
434
		if ( ! $customer ) {
435
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
436
		}
437
438 View Code Duplication
		if ( ! empty( $reassign ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
439
			if ( $reassign === $id || ! get_userdata( $reassign ) ) {
440
				return new WP_Error( 'woocommerce_rest_customer_invalid_reassign', __( 'Invalid resource id for reassignment.', 'woocommerce' ), array( 'status' => 400 ) );
441
			}
442
		}
443
444
		$request->set_param( 'context', 'edit' );
445
		$response = $this->prepare_item_for_response( $customer, $request );
446
447
		/** Include admin customer functions to get access to wp_delete_user() */
448
		require_once ABSPATH . 'wp-admin/includes/user.php';
449
450
		$result = wp_delete_user( $id, $reassign );
451
452
		if ( ! $result ) {
453
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
454
		}
455
456
		/**
457
		 * Fires after a customer is deleted via the REST API.
458
		 *
459
		 * @param WP_User          $customer The customer data.
460
		 * @param WP_REST_Response $response The response returned from the API.
461
		 * @param WP_REST_Request  $request  The request sent to the API.
462
		 */
463
		do_action( 'woocommerce_rest_delete_customer', $customer, $response, $request );
464
465
		return $response;
466
	}
467
468
	/**
469
	 * Prepare a single customer output for response.
470
	 *
471
	 * @param WP_User $customer Customer object.
472
	 * @param WP_REST_Request $request Request object.
473
	 * @return WP_REST_Response $response Response data.
474
	 */
475
	public function prepare_item_for_response( $customer, $request ) {
476
		$last_order = wc_get_customer_last_order( $customer->ID );
477
478
		$data = array(
479
			'id'               => $customer->ID,
480
			'date_created'     => wc_rest_prepare_date_response( $customer->user_registered ),
481
			'date_modified'    => $customer->last_update ? wc_rest_prepare_date_response( date( 'Y-m-d H:i:s', $customer->last_update ) ) : null,
482
			'email'            => $customer->user_email,
483
			'first_name'       => $customer->first_name,
484
			'last_name'        => $customer->last_name,
485
			'username'         => $customer->user_login,
486
			'last_order'       => array(
487
				'id'   => is_object( $last_order ) ? $last_order->id : null,
488
				'date' => is_object( $last_order ) ? wc_rest_prepare_date_response( $last_order->post->post_date_gmt ) : null
489
			),
490
			'orders_count'     => wc_get_customer_order_count( $customer->ID ),
491
			'total_spent'      => wc_format_decimal( wc_get_customer_total_spent( $customer->ID ), 2 ),
492
			'avatar_url'       => wc_get_customer_avatar_url( $customer->customer_email ),
493
			'billing'          => array(
494
				'first_name' => $customer->billing_first_name,
495
				'last_name'  => $customer->billing_last_name,
496
				'company'    => $customer->billing_company,
497
				'address_1'  => $customer->billing_address_1,
498
				'address_2'  => $customer->billing_address_2,
499
				'city'       => $customer->billing_city,
500
				'state'      => $customer->billing_state,
501
				'postcode'   => $customer->billing_postcode,
502
				'country'    => $customer->billing_country,
503
				'email'      => $customer->billing_email,
504
				'phone'      => $customer->billing_phone,
505
			),
506
			'shipping'         => array(
507
				'first_name' => $customer->shipping_first_name,
508
				'last_name'  => $customer->shipping_last_name,
509
				'company'    => $customer->shipping_company,
510
				'address_1'  => $customer->shipping_address_1,
511
				'address_2'  => $customer->shipping_address_2,
512
				'city'       => $customer->shipping_city,
513
				'state'      => $customer->shipping_state,
514
				'postcode'   => $customer->shipping_postcode,
515
				'country'    => $customer->shipping_country,
516
			),
517
		);
518
519
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
520
		$data    = $this->add_additional_fields_to_object( $data, $request );
521
		$data    = $this->filter_response_by_context( $data, $context );
522
523
		// Wrap the data in a response object.
524
		$response = rest_ensure_response( $data );
525
526
		$response->add_links( $this->prepare_links( $customer ) );
527
528
		/**
529
		 * Filter customer data returned from the REST API.
530
		 *
531
		 * @param WP_REST_Response $response  The response object.
532
		 * @param WP_User          $customer  User object used to create response.
533
		 * @param WP_REST_Request  $request   Request object.
534
		 */
535
		return apply_filters( 'woocommerce_rest_prepare_customer', $response, $customer, $request );
536
	}
537
538
	/**
539
	 * Update customer meta fields.
540
	 *
541
	 * @param WP_User $customer
542
	 * @param WP_REST_Request $request
543
	 */
544
	protected function update_customer_meta_fields( $customer, $request ) {
545
		$schema = $this->get_item_schema();
546
547
		// Customer first name.
548
		if ( isset( $request['first_name'] ) ) {
549
			update_user_meta( $customer->ID, 'first_name', wc_clean( $request['first_name'] ) );
550
		}
551
552
		// Customer last name.
553
		if ( isset( $request['last_name'] ) ) {
554
			update_user_meta( $customer->ID, 'last_name', wc_clean( $request['last_name'] ) );
555
		}
556
557
		// Customer billing address.
558 View Code Duplication
		if ( isset( $request['billing'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
559
			foreach ( array_keys( $schema['properties']['billing']['properties'] ) as $address ) {
560
				if ( isset( $request['billing'][ $address ] ) ) {
561
					update_user_meta( $customer->ID, 'billing_' . $address, wc_clean( $request['billing'][ $address ] ) );
562
				}
563
			}
564
		}
565
566
		// Customer shipping address.
567 View Code Duplication
		if ( isset( $request['shipping'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
568
			foreach ( array_keys( $schema['properties']['shipping']['properties'] ) as $address ) {
569
				if ( isset( $request['shipping'][ $address ] ) ) {
570
					update_user_meta( $customer->ID, 'shipping_' . $address, wc_clean( $request['shipping'][ $address ] ) );
571
				}
572
			}
573
		}
574
	}
575
576
	/**
577
	 * Prepare links for the request.
578
	 *
579
	 * @param WP_User $customer Customer object.
580
	 * @return array Links for the given customer.
581
	 */
582 View Code Duplication
	protected function prepare_links( $customer ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
583
		$links = array(
584
			'self' => array(
585
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer->ID ) ),
586
			),
587
			'collection' => array(
588
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
589
			),
590
		);
591
592
		return $links;
593
	}
594
595
	/**
596
	 * Get the Customer's schema, conforming to JSON Schema.
597
	 *
598
	 * @return array
599
	 */
600
	public function get_item_schema() {
601
		$schema = array(
602
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
603
			'title'      => 'customer',
604
			'type'       => 'object',
605
			'properties' => array(
606
				'id' => array(
607
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
608
					'type'        => 'integer',
609
					'context'     => array( 'view', 'edit' ),
610
					'readonly'    => true,
611
				),
612
				'date_created' => array(
613
					'description' => __( "The date the customer was created, in the site's timezone.", 'woocommerce' ),
614
					'type'        => 'date-time',
615
					'context'     => array( 'view', 'edit' ),
616
					'readonly'    => true,
617
				),
618
				'date_modified' => array(
619
					'description' => __( "The date the customer was last modified, in the site's timezone.", 'woocommerce' ),
620
					'type'        => 'date-time',
621
					'context'     => array( 'view', 'edit' ),
622
					'readonly'    => true,
623
				),
624
				'email' => array(
625
					'description' => __( 'The email address for the customer.', 'woocommerce' ),
626
					'type'        => 'string',
627
					'format'      => 'email',
628
					'context'     => array( 'view', 'edit' ),
629
				),
630
				'first_name' => array(
631
					'description' => __( 'Customer first name.', 'woocommerce' ),
632
					'type'        => 'string',
633
					'context'     => array( 'view', 'edit' ),
634
					'arg_options' => array(
635
						'sanitize_callback' => 'sanitize_text_field',
636
					),
637
				),
638
				'last_name' => array(
639
					'description' => __( 'Customer last name.', 'woocommerce' ),
640
					'type'        => 'string',
641
					'context'     => array( 'view', 'edit' ),
642
					'arg_options' => array(
643
						'sanitize_callback' => 'sanitize_text_field',
644
					),
645
				),
646
				'username' => array(
647
					'description' => __( 'Customer login name.', 'woocommerce' ),
648
					'type'        => 'string',
649
					'context'     => array( 'view', 'edit' ),
650
					'arg_options' => array(
651
						'sanitize_callback' => 'sanitize_user',
652
					),
653
				),
654
				'password' => array(
655
					'description' => __( 'Customer password.', 'woocommerce' ),
656
					'type'        => 'string',
657
					'context'     => array( 'edit' ),
658
				),
659
				'last_order' => array(
660
					'description' => __( 'Last order data.', 'woocommerce' ),
661
					'type'        => 'array',
662
					'context'     => array( 'view', 'edit' ),
663
					'readonly'    => true,
664
					'properties'  => array(
665
						'id' => array(
666
							'description' => __( 'Last order ID.', 'woocommerce' ),
667
							'type'        => 'integer',
668
							'context'     => array( 'view', 'edit' ),
669
							'readonly'    => true,
670
						),
671
						'date' => array(
672
							'description' => __( 'UTC DateTime of the customer last order.', 'woocommerce' ),
673
							'type'        => 'date-time',
674
							'context'     => array( 'view', 'edit' ),
675
							'readonly'    => true,
676
						),
677
					),
678
				),
679
				'orders_count' => array(
680
					'description' => __( 'Quantity of orders made by the customer.', 'woocommerce' ),
681
					'type'        => 'integer',
682
					'context'     => array( 'view', 'edit' ),
683
					'readonly'    => true,
684
				),
685
				'total_spent' => array(
686
					'description' => __( 'Total amount spent.', 'woocommerce' ),
687
					'type'        => 'float',
688
					'context'     => array( 'view', 'edit' ),
689
					'readonly'    => true,
690
				),
691
				'avatar_url' => array(
692
					'description' => __( 'Avatar URL.', 'woocommerce' ),
693
					'type'        => 'string',
694
					'context'     => array( 'view', 'edit' ),
695
					'readonly'    => true,
696
				),
697
				'billing' => array(
698
					'description' => __( 'List of billing address data.', 'woocommerce' ),
699
					'type'        => 'array',
700
					'context'     => array( 'view', 'edit' ),
701
					'properties' => array(
702
						'first_name' => array(
703
							'description' => __( 'First name.', 'woocommerce' ),
704
							'type'        => 'string',
705
							'context'     => array( 'view', 'edit' ),
706
						),
707
						'last_name' => array(
708
							'description' => __( 'Last name.', 'woocommerce' ),
709
							'type'        => 'string',
710
							'context'     => array( 'view', 'edit' ),
711
						),
712
						'company' => array(
713
							'description' => __( 'Company name.', 'woocommerce' ),
714
							'type'        => 'string',
715
							'context'     => array( 'view', 'edit' ),
716
						),
717
						'address_1' => array(
718
							'description' => __( 'Address line 1.', 'woocommerce' ),
719
							'type'        => 'string',
720
							'context'     => array( 'view', 'edit' ),
721
						),
722
						'address_2' => array(
723
							'description' => __( 'Address line 2.', 'woocommerce' ),
724
							'type'        => 'string',
725
							'context'     => array( 'view', 'edit' ),
726
						),
727
						'city' => array(
728
							'description' => __( 'City name.', 'woocommerce' ),
729
							'type'        => 'string',
730
							'context'     => array( 'view', 'edit' ),
731
						),
732
						'state' => array(
733
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
734
							'type'        => 'string',
735
							'context'     => array( 'view', 'edit' ),
736
						),
737
						'postcode' => array(
738
							'description' => __( 'Postal code.', 'woocommerce' ),
739
							'type'        => 'string',
740
							'context'     => array( 'view', 'edit' ),
741
						),
742
						'country' => array(
743
							'description' => __( 'ISO code of the country.', 'woocommerce' ),
744
							'type'        => 'string',
745
							'context'     => array( 'view', 'edit' ),
746
						),
747
						'email' => array(
748
							'description' => __( 'Email address.', 'woocommerce' ),
749
							'type'        => 'string',
750
							'format'      => 'email',
751
							'context'     => array( 'view', 'edit' ),
752
						),
753
						'phone' => array(
754
							'description' => __( 'Phone number.', 'woocommerce' ),
755
							'type'        => 'string',
756
							'context'     => array( 'view', 'edit' ),
757
						),
758
					),
759
				),
760
				'shipping' => array(
761
					'description' => __( 'List of shipping address data.', 'woocommerce' ),
762
					'type'        => 'array',
763
					'context'     => array( 'view', 'edit' ),
764
					'properties' => array(
765
						'first_name' => array(
766
							'description' => __( 'First name.', 'woocommerce' ),
767
							'type'        => 'string',
768
							'context'     => array( 'view', 'edit' ),
769
						),
770
						'last_name' => array(
771
							'description' => __( 'Last name.', 'woocommerce' ),
772
							'type'        => 'string',
773
							'context'     => array( 'view', 'edit' ),
774
						),
775
						'company' => array(
776
							'description' => __( 'Company name.', 'woocommerce' ),
777
							'type'        => 'string',
778
							'context'     => array( 'view', 'edit' ),
779
						),
780
						'address_1' => array(
781
							'description' => __( 'Address line 1.', 'woocommerce' ),
782
							'type'        => 'string',
783
							'context'     => array( 'view', 'edit' ),
784
						),
785
						'address_2' => array(
786
							'description' => __( 'Address line 2.', 'woocommerce' ),
787
							'type'        => 'string',
788
							'context'     => array( 'view', 'edit' ),
789
						),
790
						'city' => array(
791
							'description' => __( 'City name.', 'woocommerce' ),
792
							'type'        => 'string',
793
							'context'     => array( 'view', 'edit' ),
794
						),
795
						'state' => array(
796
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
797
							'type'        => 'string',
798
							'context'     => array( 'view', 'edit' ),
799
						),
800
						'postcode' => array(
801
							'description' => __( 'Postal code.', 'woocommerce' ),
802
							'type'        => 'string',
803
							'context'     => array( 'view', 'edit' ),
804
						),
805
						'country' => array(
806
							'description' => __( 'ISO code of the country.', 'woocommerce' ),
807
							'type'        => 'string',
808
							'context'     => array( 'view', 'edit' ),
809
						),
810
					),
811
				),
812
			),
813
		);
814
815
		return $this->add_additional_fields_schema( $schema );
816
	}
817
818
	/**
819
	 * Get role names.
820
	 *
821
	 * @return array
822
	 */
823
	protected function get_role_names() {
824
		global $wp_roles;
825
826
		return array_keys( $wp_roles->role_names );
827
	}
828
829
	/**
830
	 * Get the query params for collections.
831
	 *
832
	 * @return array
833
	 */
834
	public function get_collection_params() {
835
		$params = parent::get_collection_params();
836
837
		$params['context']['default'] = 'view';
838
839
		$params['exclude'] = array(
840
			'description'        => __( 'Ensure result set excludes specific ids.', 'woocommerce' ),
841
			'type'               => 'array',
842
			'default'            => array(),
843
			'sanitize_callback'  => 'wp_parse_id_list',
844
		);
845
		$params['include'] = array(
846
			'description'        => __( 'Limit result set to specific ids.', 'woocommerce' ),
847
			'type'               => 'array',
848
			'default'            => array(),
849
			'sanitize_callback'  => 'wp_parse_id_list',
850
		);
851
		$params['offset'] = array(
852
			'description'        => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
853
			'type'               => 'integer',
854
			'sanitize_callback'  => 'absint',
855
			'validate_callback'  => 'rest_validate_request_arg',
856
		);
857
		$params['order'] = array(
858
			'default'            => 'asc',
859
			'description'        => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
860
			'enum'               => array( 'asc', 'desc' ),
861
			'sanitize_callback'  => 'sanitize_key',
862
			'type'               => 'string',
863
			'validate_callback'  => 'rest_validate_request_arg',
864
		);
865
		$params['orderby'] = array(
866
			'default'            => 'name',
867
			'description'        => __( 'Sort collection by object attribute.', 'woocommerce' ),
868
			'enum'               => array(
869
				'id',
870
				'include',
871
				'name',
872
				'registered_date',
873
			),
874
			'sanitize_callback'  => 'sanitize_key',
875
			'type'               => 'string',
876
			'validate_callback'  => 'rest_validate_request_arg',
877
		);
878
		$params['email'] = array(
879
			'description'        => __( 'Limit result set to resources with a specific email.', 'woocommerce' ),
880
			'type'               => 'string',
881
			'format'             => 'email',
882
			'validate_callback'  => 'rest_validate_request_arg',
883
		);
884
		$params['role'] = array(
885
			'description'        => __( 'Limit result set to resources with a specific role.', 'woocommerce' ),
886
			'type'               => 'string',
887
			'default'            => 'customer',
888
			'enum'               => array_merge( array( 'all' ), $this->get_role_names() ),
889
			'validate_callback'  => 'rest_validate_request_arg',
890
		);
891
		return $params;
892
	}
893
}
894