Completed
Push — master ( f47a1d...37f03f )
by Claudio
28:06
created

WC_REST_Customers_Controller::delete_item()   D

Complexity

Conditions 9
Paths 28

Size

Total Lines 44
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 44
rs 4.909
c 1
b 1
f 0
nc 28
cc 9
eloc 20
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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 WP_REST_Controller
22
 */
23
class WC_REST_Customers_Controller extends WP_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 . '/me', array(
100
			'methods'  => WP_REST_Server::READABLE,
101
			'callback' => array( $this, 'get_current_item' ),
102
			'args'     => array(
103
				'context' => array(),
104
			),
105
			'schema' => array( $this, 'get_public_item_schema' ),
106
		) );
107
	}
108
109
	/**
110
	 * Check whether a given request has permission to read customers.
111
	 *
112
	 * @param  WP_REST_Request $request Full details about the request.
113
	 * @return WP_Error|boolean
114
	 */
115 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...
116
		if ( ! wc_rest_check_user_permissions( 'read' ) ) {
117
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
118
		}
119
120
		return true;
121
	}
122
123
	/**
124
	 * Check if a given request has access create customers.
125
	 *
126
	 * @param  WP_REST_Request $request Full details about the request.
127
	 * @return boolean
128
	 */
129 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...
130
		if ( ! wc_rest_check_user_permissions( 'create' ) ) {
131
			return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
132
		}
133
134
		return true;
135
	}
136
137
	/**
138
	 * Check if a given request has access to read a customer.
139
	 *
140
	 * @param  WP_REST_Request $request Full details about the request.
141
	 * @return WP_Error|boolean
142
	 */
143 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...
144
		$id = (int) $request['id'];
145
146
		if ( ! wc_rest_check_user_permissions( 'read', $id ) ) {
147
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
148
		}
149
150
		return true;
151
	}
152
153
	/**
154
	 * Check if a given request has access update a customer.
155
	 *
156
	 * @param  WP_REST_Request $request Full details about the request.
157
	 * @return boolean
158
	 */
159 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...
160
		$id = (int) $request['id'];
161
162
		if ( ! wc_rest_check_user_permissions( 'edit', $id ) ) {
163
			return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
164
		}
165
166
		return true;
167
	}
168
169
	/**
170
	 * Check if a given request has access delete a customer.
171
	 *
172
	 * @param  WP_REST_Request $request Full details about the request.
173
	 * @return boolean
174
	 */
175 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...
176
		$id = (int) $request['id'];
177
178
		if ( ! wc_rest_check_user_permissions( 'delete', $id ) ) {
179
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
180
		}
181
182
		return true;
183
	}
184
185
	/**
186
	 * Get all customers.
187
	 *
188
	 * @param WP_REST_Request $request Full details about the request.
189
	 * @return WP_Error|WP_REST_Response
190
	 */
191
	public function get_items( $request ) {
192
		$prepared_args = array();
193
		$prepared_args['exclude'] = $request['exclude'];
194
		$prepared_args['include'] = $request['include'];
195
		$prepared_args['order']   = $request['order'];
196
		$prepared_args['number']  = $request['per_page'];
197 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...
198
			$prepared_args['offset'] = $request['offset'];
199
		} else {
200
			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
201
		}
202
		$orderby_possibles = array(
203
			'id'              => 'ID',
204
			'include'         => 'include',
205
			'name'            => 'display_name',
206
			'registered_date' => 'registered',
207
		);
208
		$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
209
		$prepared_args['search']  = $request['search'];
210
211
		if ( '' !== $prepared_args['search'] ) {
212
			$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
213
		}
214
215
		// Filter by email.
216
		if ( ! empty( $request['email'] ) ) {
217
			$prepared_args['search']         = $request['email'];
218
			$prepared_args['search_columns'] = array( 'user_email' );
219
		}
220
221
		// Filter by role.
222
		if ( 'all' !== $request['role'] ) {
223
			$prepared_args['role'] = $request['role'];
224
		}
225
226
		/**
227
		 * Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
228
		 *
229
		 * @see https://developer.wordpress.org/reference/classes/wp_user_query/
230
		 *
231
		 * @param array           $prepared_args Array of arguments for WP_User_Query.
232
		 * @param WP_REST_Request $request       The current request.
233
		 */
234
		$prepared_args = apply_filters( 'woocommerce_rest_customer_query', $prepared_args, $request );
235
236
		$query = new WP_User_Query( $prepared_args );
237
238
		$users = array();
239
		foreach ( $query->results as $user ) {
240
			$data = $this->prepare_item_for_response( $user, $request );
241
			$users[] = $this->prepare_response_for_collection( $data );
242
		}
243
244
		$response = rest_ensure_response( $users );
245
246
		// Store pagation values for headers then unset for count query.
247
		$per_page = (int) $prepared_args['number'];
248
		$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
249
250
		$prepared_args['fields'] = 'ID';
251
252
		$total_users = $query->get_total();
253
		if ( $total_users < 1 ) {
254
			// Out-of-bounds, run the query again without LIMIT for total count.
255
			unset( $prepared_args['number'] );
256
			unset( $prepared_args['offset'] );
257
			$count_query = new WP_User_Query( $prepared_args );
258
			$total_users = $count_query->get_total();
259
		}
260
		$response->header( 'X-WP-Total', (int) $total_users );
261
		$max_pages = ceil( $total_users / $per_page );
262
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
263
264
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
265 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...
266
			$prev_page = $page - 1;
267
			if ( $prev_page > $max_pages ) {
268
				$prev_page = $max_pages;
269
			}
270
			$prev_link = add_query_arg( 'page', $prev_page, $base );
271
			$response->link_header( 'prev', $prev_link );
272
		}
273 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...
274
			$next_page = $page + 1;
275
			$next_link = add_query_arg( 'page', $next_page, $base );
276
			$response->link_header( 'next', $next_link );
277
		}
278
279
		return $response;
280
	}
281
282
	/**
283
	 * Create a single customer.
284
	 *
285
	 * @param WP_REST_Request $request Full details about the request.
286
	 * @return WP_Error|WP_REST_Response
287
	 */
288
	public function create_item( $request ) {
289
		if ( ! empty( $request['id'] ) ) {
290
			return new WP_Error( 'woocommerce_rest_customer_exists', __( 'Cannot create existing resource.', 'woocommerce' ), array( 'status' => 400 ) );
291
		}
292
293
		// Sets the username.
294
		$request['username'] = ! empty( $request['username'] ) ? $request['username'] : '';
295
296
		// Sets the password.
297
		$request['password'] = ! empty( $request['password'] ) ? $request['password'] : '';
298
299
		// Create customer.
300
		$customer_id = wc_create_new_customer( $request['email'], $request['username'], $request['password'] );;
301
		if ( is_wp_error( $customer_id ) ) {
302
			return $customer_id;
303
		}
304
305
		$customer = get_user_by( 'id', $customer_id );
306
307
		$this->update_additional_fields_for_object( $customer, $request );
308
309
		// Add customer data.
310
		$this->update_customer_meta_fields( $customer, $request );
311
312
		/**
313
		 * Fires after a customer is created or updated via the REST API.
314
		 *
315
		 * @param WP_User         $customer  Data used to create the customer.
316
		 * @param WP_REST_Request $request   Request object.
317
		 * @param boolean         $creating  True when creating customer, false when updating customer.
318
		 */
319
		do_action( 'woocommerce_rest_insert_customer', $customer, $request, true );
320
321
		$request->set_param( 'context', 'edit' );
322
		$response = $this->prepare_item_for_response( $customer, $request );
323
		$response = rest_ensure_response( $response );
324
		$response->set_status( 201 );
325
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer_id ) ) );
326
327
		return $response;
328
	}
329
330
	/**
331
	 * Get a single customer.
332
	 *
333
	 * @param WP_REST_Request $request Full details about the request.
334
	 * @return WP_Error|WP_REST_Response
335
	 */
336 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...
337
		$id       = (int) $request['id'];
338
		$customer = get_userdata( $id );
339
340
		if ( empty( $id ) || empty( $customer->ID ) ) {
341
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 404 ) );
342
		}
343
344
		$customer = $this->prepare_item_for_response( $customer, $request );
345
		$response = rest_ensure_response( $customer );
346
347
		return $response;
348
	}
349
350
	/**
351
	 * Update a single user.
352
	 *
353
	 * @param WP_REST_Request $request Full details about the request.
354
	 * @return WP_Error|WP_REST_Response
355
	 */
356
	public function update_item( $request ) {
357
		$id       = (int) $request['id'];
358
		$customer = get_userdata( $id );
359
360
		if ( ! $customer ) {
361
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
362
		}
363
364
		if ( ! empty( $request['email'] ) && email_exists( $request['email'] ) && $request['email'] !== $customer->user_email ) {
365
			return new WP_Error( 'woocommerce_rest_customer_invalid_email', __( 'Email address is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
366
		}
367
368
		if ( ! empty( $request['username'] ) && $request['username'] !== $customer->user_login ) {
369
			return new WP_Error( 'woocommerce_rest_customer_invalid_argument', __( "Username isn't editable", 'woocommerce' ), array( 'status' => 400 ) );
370
		}
371
372
		// Customer email.
373
		if ( isset( $request['email'] ) ) {
374
			wp_update_user( array( 'ID' => $customer->ID, 'user_email' => sanitize_email( $request['email'] ) ) );
375
		}
376
377
		// Customer password.
378
		if ( isset( $request['password'] ) ) {
379
			wp_update_user( array( 'ID' => $customer->ID, 'user_pass' => wc_clean( $request['password'] ) ) );
380
		}
381
382
		$this->update_additional_fields_for_object( $customer, $request );
383
384
		// Update customer data.
385
		$this->update_customer_meta_fields( $customer, $request );
386
387
		/**
388
		 * Fires after a customer is created or updated via the REST API.
389
		 *
390
		 * @param WP_User         $customer  Data used to create the customer.
391
		 * @param WP_REST_Request $request   Request object.
392
		 * @param boolean         $creating  True when creating customer, false when updating customer.
393
		 */
394
		do_action( 'woocommerce_rest_insert_customer', $customer, $request, false );
395
396
		$request->set_param( 'context', 'edit' );
397
		$response = $this->prepare_item_for_response( $customer, $request );
398
		$response = rest_ensure_response( $response );
399
		return $response;
400
	}
401
402
	/**
403
	 * Delete a single customer.
404
	 *
405
	 * @param WP_REST_Request $request Full details about the request.
406
	 * @return WP_Error|WP_REST_Response
407
	 */
408
	public function delete_item( $request ) {
409
		$id       = (int) $request['id'];
410
		$reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
411
		$force    = isset( $request['force'] ) ? (bool) $request['force'] : false;
412
413
		// We don't support trashing for this type, error out.
414
		if ( ! $force ) {
415
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Customers do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
416
		}
417
418
		$customer = get_userdata( $id );
419
		if ( ! $customer ) {
420
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
421
		}
422
423
		if ( ! empty( $reassign ) ) {
424
			if ( $reassign === $id || ! get_userdata( $reassign ) ) {
425
				return new WP_Error( 'woocommerce_rest_customer_invalid_reassign', __( 'Invalid resource id for reassignment.', 'woocommerce' ), array( 'status' => 400 ) );
426
			}
427
		}
428
429
		$request->set_param( 'context', 'edit' );
430
		$response = $this->prepare_item_for_response( $customer, $request );
431
432
		/** Include admin customer functions to get access to wp_delete_user() */
433
		require_once ABSPATH . 'wp-admin/includes/user.php';
434
435
		$result = wp_delete_user( $id, $reassign );
436
437
		if ( ! $result ) {
438
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
439
		}
440
441
		/**
442
		 * Fires after a customer is deleted via the REST API.
443
		 *
444
		 * @param WP_User          $customer The customer data.
445
		 * @param WP_REST_Response $response The response returned from the API.
446
		 * @param WP_REST_Request  $request  The request sent to the API.
447
		 */
448
		do_action( 'woocommerce_rest_delete_customer', $customer, $response, $request );
449
450
		return $response;
451
	}
452
453
	/**
454
	 * Get the current customer.
455
	 *
456
	 * @param WP_REST_Request $request Full details about the request.
457
	 * @return WP_Error|WP_REST_Response
458
	 */
459
	public function get_current_item( $request ) {
460
		$id = get_current_user_id();
461
		if ( empty( $id ) ) {
462
			return new WP_Error( 'woocommerce_rest_not_logged_in', __( 'You are not currently logged in.', 'woocommerce' ), array( 'status' => 401 ) );
463
		}
464
465
		if ( ! wc_rest_check_user_permissions( 'read', $id ) ) {
466
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
467
		}
468
469
		$customer = wp_get_current_user();
470
		$response = $this->prepare_item_for_response( $customer, $request );
471
		$response = rest_ensure_response( $response );
472
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
473
		$response->set_status( 302 );
474
475
		return $response;
476
	}
477
478
	/**
479
	 * Prepare a single customer output for response.
480
	 *
481
	 * @param WP_User $customer Customer object.
482
	 * @param WP_REST_Request $request Request object.
483
	 * @return WP_REST_Response $response Response data.
484
	 */
485
	public function prepare_item_for_response( $customer, $request ) {
486
		$last_order = wc_get_customer_last_order( $customer->ID );
487
488
		$data = array(
489
			'id'               => $customer->ID,
490
			'date_created'     => wc_rest_prepare_date_response( $customer->user_registered ),
491
			'date_modified'    => $customer->last_update ? wc_rest_prepare_date_response( date( 'Y-m-d H:i:s', $customer->last_update ) ) : null,
492
			'email'            => $customer->user_email,
493
			'first_name'       => $customer->first_name,
494
			'last_name'        => $customer->last_name,
495
			'username'         => $customer->user_login,
496
			'last_order'       => array(
497
				'id'   => is_object( $last_order ) ? $last_order->id : null,
498
				'date' => is_object( $last_order ) ? wc_rest_prepare_date_response( $last_order->post->post_date_gmt ) : null
499
			),
500
			'orders_count'     => wc_get_customer_order_count( $customer->ID ),
501
			'total_spent'      => wc_format_decimal( wc_get_customer_total_spent( $customer->ID ), 2 ),
502
			'avatar_url'       => wc_get_customer_avatar_url( $customer->customer_email ),
503
			'billing_address'  => array(
504
				'first_name' => $customer->billing_first_name,
505
				'last_name'  => $customer->billing_last_name,
506
				'company'    => $customer->billing_company,
507
				'address_1'  => $customer->billing_address_1,
508
				'address_2'  => $customer->billing_address_2,
509
				'city'       => $customer->billing_city,
510
				'state'      => $customer->billing_state,
511
				'postcode'   => $customer->billing_postcode,
512
				'country'    => $customer->billing_country,
513
				'email'      => $customer->billing_email,
514
				'phone'      => $customer->billing_phone,
515
			),
516
			'shipping_address' => array(
517
				'first_name' => $customer->shipping_first_name,
518
				'last_name'  => $customer->shipping_last_name,
519
				'company'    => $customer->shipping_company,
520
				'address_1'  => $customer->shipping_address_1,
521
				'address_2'  => $customer->shipping_address_2,
522
				'city'       => $customer->shipping_city,
523
				'state'      => $customer->shipping_state,
524
				'postcode'   => $customer->shipping_postcode,
525
				'country'    => $customer->shipping_country,
526
			),
527
		);
528
529
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
530
		$data    = $this->add_additional_fields_to_object( $data, $request );
531
		$data    = $this->filter_response_by_context( $data, $context );
532
533
		// Wrap the data in a response object.
534
		$response = rest_ensure_response( $data );
535
536
		$response->add_links( $this->prepare_links( $customer ) );
537
538
		/**
539
		 * Filter customer data returned from the REST API.
540
		 *
541
		 * @param WP_REST_Response $response  The response object.
542
		 * @param WP_User          $customer  User object used to create response.
543
		 * @param WP_REST_Request  $request   Request object.
544
		 */
545
		return apply_filters( 'woocommerce_rest_prepare_customer', $response, $customer, $request );
546
	}
547
548
	/**
549
	 * Update customer meta fields.
550
	 *
551
	 * @param WP_User $customer
552
	 * @param WP_REST_Request $request
553
	 */
554
	protected function update_customer_meta_fields( $customer, $request ) {
555
		$schema = $this->get_item_schema();
556
557
		// Customer first name.
558
		if ( isset( $request['first_name'] ) ) {
559
			update_user_meta( $customer->ID, 'first_name', wc_clean( $request['first_name'] ) );
560
		}
561
562
		// Customer last name.
563
		if ( isset( $request['last_name'] ) ) {
564
			update_user_meta( $customer->ID, 'last_name', wc_clean( $request['last_name'] ) );
565
		}
566
567
		// Customer billing address.
568 View Code Duplication
		if ( isset( $request['billing_address'] ) ) {
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...
569
			foreach ( array_keys( $schema['properties']['billing_address']['properties'] ) as $address ) {
570
				if ( isset( $request['billing_address'][ $address ] ) ) {
571
					update_user_meta( $customer->ID, 'billing_' . $address, wc_clean( $request['billing_address'][ $address ] ) );
572
				}
573
			}
574
		}
575
576
		// Customer shipping address.
577 View Code Duplication
		if ( isset( $request['shipping_address'] ) ) {
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...
578
			foreach ( array_keys( $schema['properties']['shipping_address']['properties'] ) as $address ) {
579
				if ( isset( $request['shipping_address'][ $address ] ) ) {
580
					update_user_meta( $customer->ID, 'shipping_' . $address, wc_clean( $request['shipping_address'][ $address ] ) );
581
				}
582
			}
583
		}
584
	}
585
586
	/**
587
	 * Prepare links for the request.
588
	 *
589
	 * @param WP_User $customer Customer object.
590
	 * @return array Links for the given customer.
591
	 */
592 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...
593
		$links = array(
594
			'self' => array(
595
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer->ID ) ),
596
			),
597
			'collection' => array(
598
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
599
			),
600
		);
601
602
		return $links;
603
	}
604
605
	/**
606
	 * Get the Customer's schema, conforming to JSON Schema.
607
	 *
608
	 * @return array
609
	 */
610
	public function get_item_schema() {
611
		$schema = array(
612
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
613
			'title'      => 'customer',
614
			'type'       => 'object',
615
			'properties' => array(
616
				'id' => array(
617
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
618
					'type'        => 'integer',
619
					'context'     => array( 'view', 'edit' ),
620
					'readonly'    => true,
621
				),
622
				'date_created' => array(
623
					'description' => __( "The date the customer was created, in the site's timezone.", 'woocommerce' ),
624
					'type'        => 'date-time',
625
					'context'     => array( 'view', 'edit' ),
626
					'readonly'    => true,
627
				),
628
				'date_modified' => array(
629
					'description' => __( "The date the customer was last modified, in the site's timezone.", 'woocommerce' ),
630
					'type'        => 'date-time',
631
					'context'     => array( 'view', 'edit' ),
632
					'readonly'    => true,
633
				),
634
				'email' => array(
635
					'description' => __( 'The email address for the customer.', 'woocommerce' ),
636
					'type'        => 'string',
637
					'format'      => 'email',
638
					'context'     => array( 'view', 'edit' ),
639
				),
640
				'first_name' => array(
641
					'description' => __( 'Customer first name.', 'woocommerce' ),
642
					'type'        => 'string',
643
					'context'     => array( 'view', 'edit' ),
644
					'arg_options' => array(
645
						'sanitize_callback' => 'sanitize_text_field',
646
					),
647
				),
648
				'last_name' => array(
649
					'description' => __( 'Customer last name.', 'woocommerce' ),
650
					'type'        => 'string',
651
					'context'     => array( 'view', 'edit' ),
652
					'arg_options' => array(
653
						'sanitize_callback' => 'sanitize_text_field',
654
					),
655
				),
656
				'username' => array(
657
					'description' => __( 'Customer login name.', 'woocommerce' ),
658
					'type'        => 'string',
659
					'context'     => array( 'view', 'edit' ),
660
					'arg_options' => array(
661
						'sanitize_callback' => 'sanitize_user',
662
					),
663
				),
664
				'password' => array(
665
					'description' => __( 'Customer password.', 'woocommerce' ),
666
					'type'        => 'string',
667
					'context'     => array( 'edit' ),
668
				),
669
				'last_order' => array(
670
					'description' => __( 'Last order data.', 'woocommerce' ),
671
					'type'        => 'object',
672
					'context'     => array( 'view', 'edit' ),
673
					'readonly'    => true,
674
					'properties'  => array(
675
						'id' => array(
676
							'description' => __( 'Last order ID.', 'woocommerce' ),
677
							'type'        => 'integer',
678
							'context'     => array( 'view', 'edit' ),
679
							'readonly'    => true,
680
						),
681
						'date' => array(
682
							'description' => __( 'UTC DateTime of the customer last order.', 'woocommerce' ),
683
							'type'        => 'date-time',
684
							'context'     => array( 'view', 'edit' ),
685
							'readonly'    => true,
686
						),
687
					),
688
				),
689
				'orders_count' => array(
690
					'description' => __( 'Quantity of orders made by the customer.', 'woocommerce' ),
691
					'type'        => 'integer',
692
					'context'     => array( 'view', 'edit' ),
693
					'readonly'    => true,
694
				),
695
				'total_spent' => array(
696
					'description' => __( 'Total amount spent.', 'woocommerce' ),
697
					'type'        => 'float',
698
					'context'     => array( 'view', 'edit' ),
699
					'readonly'    => true,
700
				),
701
				'avatar_url' => array(
702
					'description' => __( 'Avatar URL.', 'woocommerce' ),
703
					'type'        => 'string',
704
					'context'     => array( 'view', 'edit' ),
705
					'readonly'    => true,
706
				),
707
				'billing_address' => array(
708
					'description' => __( 'List of billing address data.', 'woocommerce' ),
709
					'type'        => 'object',
710
					'context'     => array( 'view', 'edit' ),
711
					'properties' => array(
712
						'first_name' => array(
713
							'description' => __( 'First name.', 'woocommerce' ),
714
							'type'        => 'string',
715
							'context'     => array( 'view', 'edit' ),
716
						),
717
						'last_name' => array(
718
							'description' => __( 'Last name.', 'woocommerce' ),
719
							'type'        => 'string',
720
							'context'     => array( 'view', 'edit' ),
721
						),
722
						'company' => array(
723
							'description' => __( 'Company name.', 'woocommerce' ),
724
							'type'        => 'string',
725
							'context'     => array( 'view', 'edit' ),
726
						),
727
						'address_1' => array(
728
							'description' => __( 'Address line 1.', 'woocommerce' ),
729
							'type'        => 'string',
730
							'context'     => array( 'view', 'edit' ),
731
						),
732
						'address_2' => array(
733
							'description' => __( 'Address line 2.', 'woocommerce' ),
734
							'type'        => 'string',
735
							'context'     => array( 'view', 'edit' ),
736
						),
737
						'city' => array(
738
							'description' => __( 'City name.', 'woocommerce' ),
739
							'type'        => 'string',
740
							'context'     => array( 'view', 'edit' ),
741
						),
742
						'state' => array(
743
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
744
							'type'        => 'string',
745
							'context'     => array( 'view', 'edit' ),
746
						),
747
						'postcode' => array(
748
							'description' => __( 'Postal code.', 'woocommerce' ),
749
							'type'        => 'string',
750
							'context'     => array( 'view', 'edit' ),
751
						),
752
						'country' => array(
753
							'description' => __( 'ISO code of the country.', 'woocommerce' ),
754
							'type'        => 'string',
755
							'context'     => array( 'view', 'edit' ),
756
						),
757
						'email' => array(
758
							'description' => __( 'Email address.', 'woocommerce' ),
759
							'type'        => 'string',
760
							'format'      => 'email',
761
							'context'     => array( 'view', 'edit' ),
762
						),
763
						'phone' => array(
764
							'description' => __( 'Phone number.', 'woocommerce' ),
765
							'type'        => 'string',
766
							'context'     => array( 'view', 'edit' ),
767
						),
768
					),
769
				),
770
				'shipping_address' => array(
771
					'description' => __( 'List of shipping address data.', 'woocommerce' ),
772
					'type'        => 'object',
773
					'context'     => array( 'view', 'edit' ),
774
					'properties' => array(
775
						'first_name' => array(
776
							'description' => __( 'First name.', 'woocommerce' ),
777
							'type'        => 'string',
778
							'context'     => array( 'view', 'edit' ),
779
						),
780
						'last_name' => array(
781
							'description' => __( 'Last name.', 'woocommerce' ),
782
							'type'        => 'string',
783
							'context'     => array( 'view', 'edit' ),
784
						),
785
						'company' => array(
786
							'description' => __( 'Company name.', 'woocommerce' ),
787
							'type'        => 'string',
788
							'context'     => array( 'view', 'edit' ),
789
						),
790
						'address_1' => array(
791
							'description' => __( 'Address line 1.', 'woocommerce' ),
792
							'type'        => 'string',
793
							'context'     => array( 'view', 'edit' ),
794
						),
795
						'address_2' => array(
796
							'description' => __( 'Address line 2.', 'woocommerce' ),
797
							'type'        => 'string',
798
							'context'     => array( 'view', 'edit' ),
799
						),
800
						'city' => array(
801
							'description' => __( 'City name.', 'woocommerce' ),
802
							'type'        => 'string',
803
							'context'     => array( 'view', 'edit' ),
804
						),
805
						'state' => array(
806
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
807
							'type'        => 'string',
808
							'context'     => array( 'view', 'edit' ),
809
						),
810
						'postcode' => array(
811
							'description' => __( 'Postal code.', 'woocommerce' ),
812
							'type'        => 'string',
813
							'context'     => array( 'view', 'edit' ),
814
						),
815
						'country' => array(
816
							'description' => __( 'ISO code of the country.', 'woocommerce' ),
817
							'type'        => 'string',
818
							'context'     => array( 'view', 'edit' ),
819
						),
820
					),
821
				),
822
			),
823
		);
824
825
		return $this->add_additional_fields_schema( $schema );
826
	}
827
828
	/**
829
	 * Get role names.
830
	 *
831
	 * @return array
832
	 */
833
	protected function get_role_names() {
834
		global $wp_roles;
835
836
		return array_keys( $wp_roles->role_names );
837
	}
838
839
	/**
840
	 * Get the query params for collections.
841
	 *
842
	 * @return array
843
	 */
844
	public function get_collection_params() {
845
		$params = parent::get_collection_params();
846
847
		$params['context']['default'] = 'view';
848
849
		$params['exclude'] = array(
850
			'description'        => __( 'Ensure result set excludes specific ids.', 'woocommerce' ),
851
			'type'               => 'array',
852
			'default'            => array(),
853
			'sanitize_callback'  => 'wp_parse_id_list',
854
		);
855
		$params['include'] = array(
856
			'description'        => __( 'Limit result set to specific ids.', 'woocommerce' ),
857
			'type'               => 'array',
858
			'default'            => array(),
859
			'sanitize_callback'  => 'wp_parse_id_list',
860
		);
861
		$params['offset'] = array(
862
			'description'        => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
863
			'type'               => 'integer',
864
			'sanitize_callback'  => 'absint',
865
			'validate_callback'  => 'rest_validate_request_arg',
866
		);
867
		$params['order'] = array(
868
			'default'            => 'asc',
869
			'description'        => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
870
			'enum'               => array( 'asc', 'desc' ),
871
			'sanitize_callback'  => 'sanitize_key',
872
			'type'               => 'string',
873
			'validate_callback'  => 'rest_validate_request_arg',
874
		);
875
		$params['orderby'] = array(
876
			'default'            => 'name',
877
			'description'        => __( 'Sort collection by object attribute.', 'woocommerce' ),
878
			'enum'               => array(
879
				'id',
880
				'include',
881
				'name',
882
				'registered_date',
883
			),
884
			'sanitize_callback'  => 'sanitize_key',
885
			'type'               => 'string',
886
			'validate_callback'  => 'rest_validate_request_arg',
887
		);
888
		$params['email'] = array(
889
			'description'        => __( 'Limit result set to resources with a specific email.', 'woocommerce' ),
890
			'type'               => 'string',
891
			'format'             => 'email',
892
			'validate_callback'  => 'rest_validate_request_arg',
893
		);
894
		$params['role'] = array(
895
			'description'        => __( 'Limit result set to resources with a specific role.', 'woocommerce' ),
896
			'type'               => 'string',
897
			'default'            => 'customer',
898
			'enum'               => array_merge( array( 'all' ), $this->get_role_names() ),
899
			'validate_callback'  => 'rest_validate_request_arg',
900
		);
901
		return $params;
902
	}
903
}
904