Completed
Push — master ( dac471...2b1d4e )
by Claudio
07:25
created

batch_items_permissions_check()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 7
Ratio 100 %

Importance

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