PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.1.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.1.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / api / customer.php

customer.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.1.0, at includes/api/customer.php

838 lines 27.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\API;
4
5 use StoreEngine\Classes\Customer as CustomerEntity;
6 use StoreEngine\Classes\Exceptions\StoreEngineInvalidArgumentException;
7 use StoreEngine\Classes\Exceptions\StoreEngineInvalidOrderStatusException;
8 use StoreEngine\Classes\OrderCollection;
9 use StoreEngine\Utils\Helper;
10 use WP_Error;
11 use WP_REST_Request;
12 use WP_REST_Response;
13 use WP_REST_Server;
14 use WP_REST_Users_Controller;
15 use WP_User_Query;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Customer API
23 *
24 * @see WP_REST_Users_Controller
25 */
26 class Customer extends WP_REST_Users_Controller {
27 public static function init() {
28 $self = new self();
29 $self->namespace = STOREENGINE_PLUGIN_SLUG . '/v1';
30 $self->rest_base = 'customer';
31
32 add_action( 'rest_api_init', [ $self, 'register_routes' ] );
33 }
34
35 public function __construct() {
36 parent::__construct();
37 $this->namespace = STOREENGINE_PLUGIN_SLUG . '/v1';
38 $this->rest_base = 'customer';
39 }
40
41 public function register_routes() {
42 register_rest_route( $this->namespace, '/' . $this->rest_base, [
43 [
44 'methods' => WP_REST_Server::READABLE,
45 'callback' => [ $this, 'get_items' ],
46 'permission_callback' => [ $this, 'get_permission_check' ],
47 'args' => $this->get_collection_params(),
48 ],
49 [
50 'methods' => WP_REST_Server::CREATABLE,
51 'callback' => [ $this, 'create_item' ],
52 'permission_callback' => [ $this, 'get_permission_check' ],
53 'args' => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), [
54 'email' => [
55 'required' => true,
56 'type' => 'string',
57 'description' => __( 'New user email address.', 'storeengine' ),
58 ],
59 'username' => [
60 'required' => true,
61 'description' => __( 'New user username.', 'storeengine' ),
62 'type' => 'string',
63 ],
64 'password' => [
65 'required' => false,
66 'description' => __( 'New user password.', 'storeengine' ),
67 'type' => 'string',
68 ],
69 ] ),
70 ],
71 'schema' => [ $this, 'get_public_item_schema' ],
72 ] );
73 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', [
74 'args' => [
75 'id' => [
76 'description' => __( 'Unique identifier for the resource.', 'storeengine' ),
77 'type' => 'integer',
78 ],
79 ],
80 [
81 'methods' => WP_REST_Server::READABLE,
82 'callback' => [ $this, 'get_item' ],
83 'permission_callback' => [ $this, 'get_permission_check' ],
84 'args' => [
85 'context' => $this->get_context_param( [ 'default' => 'view' ] ),
86 ],
87 ],
88 [
89 'methods' => WP_REST_Server::EDITABLE,
90 'callback' => [ $this, 'update_item' ],
91 'permission_callback' => [ $this, 'get_permission_check' ],
92 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
93 ],
94 [
95 'methods' => WP_REST_Server::DELETABLE,
96 'callback' => [ $this, 'delete_item' ],
97 'permission_callback' => [ $this, 'get_permission_check' ],
98 'args' => [
99 'force' => [
100 'default' => false,
101 'type' => 'boolean',
102 'description' => __( 'Required to be true, as resource does not support trashing.', 'storeengine' ),
103 ],
104 'reassign' => [
105 'default' => 0,
106 'type' => 'integer',
107 'description' => __( 'ID to reassign posts to.', 'storeengine' ),
108 ],
109 ],
110 ],
111 'schema' => [ $this, 'get_public_item_schema' ],
112 ] );
113 }
114
115 public function get_permission_check() {
116 return Helper::check_rest_user_cap( 'manage_options' );
117 }
118
119 /**
120 * Create a single customer.
121 *
122 * @param WP_REST_Request $request Full details about the request.
123 *
124 * @return WP_Error|WP_REST_Response
125 */
126 public function create_item( $request ) {
127 if ( ! empty( $request['id'] ) ) {
128 return new WP_Error( 'rest_customer_exists', __( 'Cannot create existing resource.', 'storeengine' ), [ 'status' => 400 ] );
129 }
130
131 // Sets the username.
132 $request['username'] = ! empty( $request['username'] ) ? $request['username'] : '';
133
134 // Sets the password.
135 $request['password'] = ! empty( $request['password'] ) ? $request['password'] : '';
136
137 $customer = new CustomerEntity();
138
139 $customer->set_email( sanitize_email( $request['email'] ) );
140 $customer->set_password( sanitize_text_field( $request['password'] ) );
141 $customer->set_username( sanitize_text_field( $request['username'] ) );
142
143 $saved = $customer->save();
144
145 if ( is_wp_error( $saved ) ) {
146 return $saved;
147 }
148
149 if ( ! $customer->get_id() ) {
150 return new WP_Error( 'rest_cannot_create', __( 'Cannot create the customer.', 'storeengine' ), [ 'status' => 400 ] );
151 }
152
153 // @FIXME send new user a welcome email.
154 $this->update_customer_meta_fields( $customer, $request );
155
156 $response = rest_ensure_response( $this->rest_prepare_item_for_response( $customer, $request ) );
157 $response->add_links( $this->prepare_links( get_userdata( $customer->get_id() ) ) );
158 $response->set_status( 201 );
159 $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $customer->get_id() ) ) );
160
161 return $response;
162 }
163
164 /**
165 * Update a single user.
166 *
167 * @param WP_REST_Request $request Full details about the request.
168 *
169 * @return WP_Error|WP_REST_Response
170 */
171 public function update_item( $request ) {
172 $customer = Helper::get_customer( (int) $request['id'] );
173
174 if ( ! $customer ) {
175 return new WP_Error( 'rest_customer_invalid_id', __( 'Invalid customer ID.', 'storeengine' ), [ 'status' => 404 ] );
176 }
177
178 if ( ! empty( $request['email'] ) && email_exists( $request['email'] ) && $request['email'] !== $customer->get_email() ) {
179 return new WP_Error( 'rest_customer_invalid_email', __( 'Email address is invalid.', 'storeengine' ), [ 'status' => 400 ] );
180 }
181
182 if ( ! empty( $request['username'] ) && $request['username'] !== $customer->get_username() ) {
183 return new WP_Error( 'rest_customer_invalid_argument', __( "Username isn't editable.", 'storeengine' ), [ 'status' => 400 ] );
184 }
185
186 // Customer email.
187 if ( isset( $request['email'] ) ) {
188 $customer->set_email( sanitize_email( $request['email'] ) );
189 }
190
191 // Customer password.
192 if ( isset( $request['password'] ) ) {
193 $customer->set_password( $request['password'] );
194 }
195
196 $customer->save();
197
198 $this->update_customer_meta_fields( $customer, $request );
199
200 if ( ! is_user_member_of_blog( $customer->get_id() ) ) {
201 $user_data = get_userdata( $customer->get_id() );
202 $user_data->add_role( 'storeengine_customer' );
203 }
204
205 return rest_ensure_response( $this->rest_prepare_item_for_response( $customer, $request ) );
206 }
207
208 /**
209 * Delete a single customer.
210 *
211 * @param WP_REST_Request $request Full details about the request.
212 *
213 * @return WP_Error|WP_REST_Response
214 */
215 public function delete_item( $request ) {
216 $id = (int) $request['id'];
217 $reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
218 $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
219 // @TODO handle multisite.
220
221 // We don't support trashing for this type, error out.
222 if ( ! $force ) {
223 return new WP_Error( 'rest_trash_not_supported', __( 'Customers do not support trashing.', 'storeengine' ), [ 'status' => 501 ] );
224 }
225
226 $customer = Helper::get_customer( (int) $request['id'] );
227
228 if ( ! $customer ) {
229 return new WP_Error( 'rest_customer_invalid_id', __( 'Invalid customer ID.', 'storeengine' ), [ 'status' => 404 ] );
230 }
231
232 if ( ! empty( $reassign ) ) {
233 if ( $reassign === $id || ! get_userdata( $reassign ) ) {
234 return new WP_Error( 'rest_customer_invalid_reassign', __( 'Invalid resource id for reassignment.', 'storeengine' ), [ 'status' => 400 ] );
235 }
236 }
237
238 $request->set_param( 'context', 'edit' );
239 $previous = $this->rest_prepare_item_for_response( $customer, $request );
240
241 $result = parent::delete_item( $request );
242
243 if ( is_wp_error( $result ) ) {
244 return new WP_Error( 'rest_cannot_delete', __( 'The customer cannot be deleted.', 'storeengine' ), [ 'status' => 500 ] );
245 }
246
247 return rest_ensure_response( [
248 'deleted' => true,
249 'previous' => $previous,
250 ] );
251 }
252
253 protected function update_customer_meta_fields( CustomerEntity $customer, $request ) {
254 if ( ! $customer->get_id() ) {
255 return;
256 }
257
258 $schema = $this->get_item_schema();
259
260 if ( ! empty( $request['first_name'] ) ) {
261 $customer->set_first_name( sanitize_text_field( $request['first_name'] ) );
262 }
263
264 if ( ! empty( $request['last_name'] ) ) {
265 $customer->set_last_name( sanitize_text_field( $request['last_name'] ) );
266 }
267
268 $subscribe_to_email = array_key_exists( 'subscribe_to_email', $request->get_params() ) ? $request['subscribe_to_email'] : false;
269 $customer->set_subscribe_to_email( $subscribe_to_email );
270
271 // Customer billing address.
272 if ( isset( $request['billing_address'] ) ) {
273 foreach ( array_keys( $schema['properties']['billing_address']['properties'] ) as $field ) {
274 if ( isset( $request['billing_address'][ $field ] ) && is_callable( [
275 $customer,
276 "set_billing_{$field}",
277 ] ) ) {
278 $customer->{"set_billing_{$field}"}( $request['billing_address'][ $field ] );
279 }
280 }
281 }
282
283 // Customer shipping address.
284 if ( isset( $request['shipping_address'] ) ) {
285 foreach ( array_keys( $schema['properties']['shipping_address']['properties'] ) as $field ) {
286 if ( isset( $request['shipping_address'][ $field ] ) && is_callable( [
287 $customer,
288 "set_shipping_{$field}",
289 ] ) ) {
290 $customer->{"set_shipping_{$field}"}( $request['shipping_address'][ $field ] );
291 }
292 }
293 }
294
295 $customer->save();
296
297 // Meta data.
298 if ( isset( $request['meta_data'] ) ) {
299 if ( is_array( $request['meta_data'] ) ) {
300 foreach ( $request['meta_data'] as $meta ) {
301 // @TODO implement the use of meta id, it will enable safely deletion of the data.
302 update_user_meta( $customer->get_id(), $meta['key'], $meta['value'] );
303 }
304 }
305 }
306 }
307
308 public function get_item( $request ) {
309 $user = get_userdata( $request->get_param( 'id' ) );
310
311 if ( ! $user || ! $user->exists() ) {
312 return new WP_Error( 'rest_customer_invalid_id', __( 'Customer not found.', 'storeengine' ), [ 'status' => 404 ] );
313 }
314
315 $customer = Helper::get_customer( $user->ID );
316
317 $response = rest_ensure_response( $this->rest_prepare_item_for_response( $customer, $request ) );
318 $response->add_links( $this->prepare_links( get_userdata( $customer->get_id() ) ) );
319
320 return $response;
321 }
322
323 /**
324 * Prepare customer data for response.
325 *
326 * @param CustomerEntity $customer
327 * @param WP_REST_Request $request
328 *
329 * @return array
330 * @throws StoreEngineInvalidArgumentException
331 * @throws StoreEngineInvalidOrderStatusException
332 */
333 protected function rest_prepare_item_for_response( CustomerEntity $customer, WP_REST_Request $request ): array {
334 // @FIXME missing limit, without limit system might crush;
335 // @TODO Move order list, subscription list and purchase history to separate endpoints with proper pagination.
336 // Client should request for these data separately.
337 $orders = $this->get_formatted_orders( $customer->get_id() );
338 $purchase_items = OrderCollection::get_purchase_history( $customer->get_id() );
339 $total_spent = OrderCollection::get_total_spent( $customer->get_id() );
340
341 return apply_filters( 'storeengine/api/customer/customer_data',
342 [
343 'id' => $customer->get_id(),
344 'user_registered' => $customer->get_user_registered() ? $customer->get_user_registered()->format('Y-m-d H:i:s') : null,
345 'name' => $customer->get_name(),
346 'first_name' => $customer->get_first_name(),
347 'last_name' => $customer->get_last_name(),
348 'email' => $customer->get_email(),
349 'email_hash' => md5( $customer->get_email() ),
350 'total_orders' => $customer->get_total_orders(),
351 'subscribe_to_email' => $customer->has_subscribe_to_email(),
352 'orders' => $orders,
353 'total_spent' => $total_spent,
354 'is_paying_customer' => $total_spent > 0,
355 'billing_address' => [
356 'first_name' => $customer->get_billing_first_name(),
357 'last_name' => $customer->get_billing_last_name(),
358 'email' => $customer->get_billing_email(),
359 'phone' => $customer->get_billing_phone(),
360 'company' => $customer->get_billing_company(),
361 'address_1' => $customer->get_billing_address_1(),
362 'address_2' => $customer->get_billing_address_2(),
363 'city' => $customer->get_billing_city(),
364 'state' => $customer->get_billing_state(),
365 'country' => $customer->get_billing_country(),
366 'postcode' => $customer->get_billing_postcode(),
367 ],
368 'shipping_address' => [
369 'first_name' => $customer->get_shipping_first_name(),
370 'last_name' => $customer->get_shipping_last_name(),
371 'email' => $customer->get_shipping_email(),
372 'phone' => $customer->get_shipping_phone(),
373 'address_1' => $customer->get_shipping_address_1(),
374 'address_2' => $customer->get_shipping_address_2(),
375 'city' => $customer->get_shipping_city(),
376 'state' => $customer->get_shipping_state(),
377 'country' => $customer->get_shipping_country(),
378 'postcode' => $customer->get_shipping_postcode(),
379 ],
380 'purchase_items' => $purchase_items,
381 'payments' => [],
382 ],
383 $customer,
384 $request
385 );
386 }
387
388 public function get_items( $request ) {
389 $per_page = $request->get_param( 'per_page' );
390 $page = max( 1, absint( $request->get_param( 'page' ) ) );
391 $args = [
392 'number' => $per_page,
393 'offset' => ! empty( $request['offset'] ) ? $request['offset'] : ( $page - 1 ) * $per_page,
394 'order' => $request->get_param( 'order' ),
395 'include' => $request->get_param( 'include' ),
396 // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
397 'exclude' => $request->get_param( 'exclude' ),
398 ];
399
400 $orderby_possibles = [
401 'id' => 'ID',
402 'include' => 'include',
403 'name' => 'display_name',
404 'registered_date' => 'registered',
405 ];
406 $args['orderby'] = $orderby_possibles[ $request['orderby'] ];
407
408 $search = $request->get_param( 'search' );
409 if ( ! empty( $search ) ) {
410 $args['search'] = '*' . $search . '*';
411 }
412
413 // Filter by email.
414 if ( ! empty( $request['email'] ) ) {
415 $args['search'] = $request['email'];
416 $args['search_columns'] = [ 'user_email' ];
417 }
418
419 $query = new WP_User_Query( $args );
420 $customers = [];
421
422 foreach ( $query->get_results() as $user ) {
423 $customer = new CustomerEntity();
424 $customer->set_data( $user );
425 $response = rest_ensure_response( $this->rest_prepare_item_for_response( $customer, $request ) );
426 $response->add_links( $this->prepare_links( get_userdata( $customer->get_id() ) ) );
427 $customers[] = $this->prepare_response_for_collection( $response );
428 }
429
430 $response = rest_ensure_response( $customers );
431
432 // Store pagination values for headers then unset for count query.
433 $per_page = (int) $args['number'];
434 $page = ceil( ( ( (int) $args['offset'] ) / $per_page ) + 1 );
435
436 $args['fields'] = 'ID';
437
438 $total_users = $query->get_total();
439 if ( $total_users < 1 ) {
440 // Out-of-bounds, run the query again without LIMIT for total count.
441 unset( $args['number'] );
442 unset( $args['offset'] );
443 $count_query = new WP_User_Query( $args );
444 $total_users = $count_query->get_total();
445 }
446
447 $max_pages = ceil( $total_users / $per_page );
448 $response->header( 'X-WP-Total', (int) $total_users );
449 $response->header( 'X-WP-TotalPages', (int) $max_pages );
450
451 $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
452
453 if ( $page > 1 ) {
454 $prev_page = $page - 1;
455
456 if ( $prev_page > $max_pages ) {
457 $prev_page = $max_pages;
458 }
459
460 $prev_link = add_query_arg( 'page', $prev_page, $base );
461 $response->link_header( 'prev', $prev_link );
462 }
463
464 if ( $max_pages > $page ) {
465 $next_page = $page + 1;
466 $next_link = add_query_arg( 'page', $next_page, $base );
467 $response->link_header( 'next', $next_link );
468 }
469
470 return $response;
471 }
472
473 /**
474 * Get orders.
475 *
476 * @param int $customer_id
477 *
478 * @return array
479 * @throws StoreEngineInvalidArgumentException
480 * @throws StoreEngineInvalidOrderStatusException
481 */
482 protected function get_formatted_orders( int $customer_id ): array {
483 $orders = [];
484 $query = new OrderCollection( [
485 'per_page' => - 1,
486 'page' => 1,
487 'where' => [
488 [
489 'key' => 'type',
490 'value' => 'order',
491 ],
492 [
493 'key' => 'customer_id',
494 'value' => $customer_id,
495 ],
496 [
497 'key' => 'status',
498 'value' => [ 'draft', 'auto-draft', 'trash' ],
499 'compare' => 'NOT IN',
500 ],
501 ],
502 ] );
503
504 foreach ( $query->get_results() as $order ) {
505 $orders[] = [
506 'order_id' => $order->get_id(),
507 'total_items' => array_sum( array_map( fn( $order_item ) => $order_item->get_quantity(), $order->get_items() ) ),
508 'refunds_total' => $order->get_total_refunded(),
509 'total' => (float) $order->get_total(),
510 'status' => $order->get_status(),
511 'status_title' => $order->get_status_title(),
512 'payment_method' => $order->get_payment_method_title(),
513 'date' => $order->get_date_created_gmt()->format( 'Y-m-d H:i:s' ),
514 ];
515 }
516
517 return $orders;
518 }
519
520 protected function prepare_links( $user ): array {
521 return [
522 'self' => [
523 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ),
524 ],
525 'collection' => [
526 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
527 ],
528 ];
529 }
530
531 /**
532 * Get the Customer's schema, conforming to JSON Schema.
533 *
534 * @return array
535 */
536 public function get_item_schema(): array {
537 $schema = [
538 '$schema' => 'http://json-schema.org/draft-04/schema#',
539 'title' => 'customer',
540 'type' => 'object',
541 'properties' => [
542 'id' => [
543 'description' => __( 'Unique identifier for the resource.', 'storeengine' ),
544 'type' => 'integer',
545 'context' => [ 'view', 'edit' ],
546 'readonly' => true,
547 ],
548 'user_registered' => [
549 'description' => __( "The date the customer was registered, in the site's timezone.", 'storeengine' ),
550 'type' => 'date-time',
551 'context' => [ 'view', 'edit' ],
552 'readonly' => true,
553 ],
554 'email' => [
555 'description' => __( 'The email address for the customer.', 'storeengine' ),
556 'type' => 'string',
557 'format' => 'email',
558 'context' => [ 'view', 'edit' ],
559 ],
560 'first_name' => [
561 'description' => __( 'Customer first name.', 'storeengine' ),
562 'type' => 'string',
563 'context' => [ 'view', 'edit' ],
564 'arg_options' => [
565 'sanitize_callback' => 'sanitize_text_field',
566 'validate_callback' => 'rest_validate_request_arg',
567 ],
568 ],
569 'last_name' => [
570 'description' => __( 'Customer last name.', 'storeengine' ),
571 'type' => 'string',
572 'context' => [ 'view', 'edit' ],
573 'arg_options' => [
574 'sanitize_callback' => 'sanitize_text_field',
575 'validate_callback' => 'rest_validate_request_arg',
576 ],
577 ],
578 'role' => [
579 'description' => __( 'Customer role.', 'storeengine' ),
580 'type' => 'string',
581 'context' => [ 'view', 'edit' ],
582 'readonly' => true,
583 ],
584 'username' => [
585 'description' => __( 'Customer login name.', 'storeengine' ),
586 'type' => 'string',
587 'context' => [ 'view', 'edit' ],
588 'arg_options' => [
589 'sanitize_callback' => 'sanitize_user',
590 'validate_callback' => 'rest_validate_request_arg',
591 ],
592 'default' => '',
593 ],
594 'password' => [
595 'description' => __( 'Customer password.', 'storeengine' ),
596 'type' => 'string',
597 'context' => [ 'edit' ],
598 'default' => '',
599 ],
600 'billing_address' => [
601 'description' => __( 'List of billing address data.', 'storeengine' ),
602 'type' => 'object',
603 'context' => [ 'view', 'edit' ],
604 'properties' => [
605 'first_name' => [
606 'description' => __( 'First name.', 'storeengine' ),
607 'type' => 'string',
608 'context' => [ 'view', 'edit' ],
609 ],
610 'last_name' => [
611 'description' => __( 'Last name.', 'storeengine' ),
612 'type' => 'string',
613 'context' => [ 'view', 'edit' ],
614 ],
615 'company' => [
616 'description' => __( 'Company name.', 'storeengine' ),
617 'type' => 'string',
618 'context' => [ 'view', 'edit' ],
619 ],
620 'address_1' => [
621 'description' => __( 'Address line 1', 'storeengine' ),
622 'type' => 'string',
623 'context' => [ 'view', 'edit' ],
624 ],
625 'address_2' => [
626 'description' => __( 'Address line 2', 'storeengine' ),
627 'type' => 'string',
628 'context' => [ 'view', 'edit' ],
629 ],
630 'city' => [
631 'description' => __( 'City name.', 'storeengine' ),
632 'type' => 'string',
633 'context' => [ 'view', 'edit' ],
634 ],
635 'state' => [
636 'description' => __( 'ISO code or name of the state, province or district.', 'storeengine' ),
637 'type' => 'string',
638 'context' => [ 'view', 'edit' ],
639 ],
640 'postcode' => [
641 'description' => __( 'Postal code.', 'storeengine' ),
642 'type' => 'string',
643 'context' => [ 'view', 'edit' ],
644 ],
645 'country' => [
646 'description' => __( 'ISO code of the country.', 'storeengine' ),
647 'type' => 'string',
648 'context' => [ 'view', 'edit' ],
649 ],
650 'email' => [
651 'description' => __( 'Email address.', 'storeengine' ),
652 'type' => 'string',
653 'format' => 'email',
654 'context' => [ 'view', 'edit' ],
655 ],
656 'phone' => [
657 'description' => __( 'Phone number.', 'storeengine' ),
658 'type' => 'string',
659 'context' => [ 'view', 'edit' ],
660 ],
661 ],
662 ],
663 'shipping_address' => [
664 'description' => __( 'List of shipping address data.', 'storeengine' ),
665 'type' => 'object',
666 'context' => [ 'view', 'edit' ],
667 'properties' => [
668 'first_name' => [
669 'description' => __( 'First name.', 'storeengine' ),
670 'type' => 'string',
671 'context' => [ 'view', 'edit' ],
672 ],
673 'last_name' => [
674 'description' => __( 'Last name.', 'storeengine' ),
675 'type' => 'string',
676 'context' => [ 'view', 'edit' ],
677 ],
678 'company' => [
679 'description' => __( 'Company name.', 'storeengine' ),
680 'type' => 'string',
681 'context' => [ 'view', 'edit' ],
682 ],
683 'address_1' => [
684 'description' => __( 'Address line 1', 'storeengine' ),
685 'type' => 'string',
686 'context' => [ 'view', 'edit' ],
687 ],
688 'address_2' => [
689 'description' => __( 'Address line 2', 'storeengine' ),
690 'type' => 'string',
691 'context' => [ 'view', 'edit' ],
692 ],
693 'city' => [
694 'description' => __( 'City name.', 'storeengine' ),
695 'type' => 'string',
696 'context' => [ 'view', 'edit' ],
697 ],
698 'state' => [
699 'description' => __( 'ISO code or name of the state, province or district.', 'storeengine' ),
700 'type' => 'string',
701 'context' => [ 'view', 'edit' ],
702 ],
703 'postcode' => [
704 'description' => __( 'Postal code.', 'storeengine' ),
705 'type' => 'string',
706 'context' => [ 'view', 'edit' ],
707 ],
708 'country' => [
709 'description' => __( 'ISO code of the country.', 'storeengine' ),
710 'type' => 'string',
711 'context' => [ 'view', 'edit' ],
712 ],
713 ],
714 ],
715 'is_paying_customer' => [
716 'description' => __( 'Is the customer a paying customer?', 'storeengine' ),
717 'type' => 'bool',
718 'context' => [ 'view', 'edit' ],
719 'readonly' => true,
720 ],
721 'total_orders' => [
722 'description' => __( 'Quantity of orders made by the customer.', 'storeengine' ),
723 'type' => 'integer',
724 'context' => [ 'view', 'edit' ],
725 'readonly' => true,
726 ],
727 'total_spent' => [
728 'description' => __( 'Total amount spent.', 'storeengine' ),
729 'type' => 'string',
730 'context' => [ 'view', 'edit' ],
731 'readonly' => true,
732 ],
733 'subscribe_to_email' => [
734 'description' => __( 'Is the customer subscribed newsletter.', 'storeengine' ),
735 'type' => 'boolean',
736 'context' => [ 'view', 'edit' ],
737 'default' => false,
738 ],
739 'avatar_url' => [
740 'description' => __( 'Avatar URL.', 'storeengine' ),
741 'type' => 'string',
742 'context' => [ 'view', 'edit' ],
743 'readonly' => true,
744 ],
745 'meta_data' => [
746 'description' => __( 'Meta data.', 'storeengine' ),
747 'type' => 'array',
748 'context' => [ 'view', 'edit' ],
749 'items' => [
750 'type' => 'object',
751 'properties' => [
752 // @TODO implement the use of meta id, it will enable safely deletion of the data.
753 'key' => [
754 'description' => __( 'Meta key.', 'storeengine' ),
755 'type' => 'string',
756 'context' => [ 'view', 'edit' ],
757 'readonly' => true,
758 ],
759 'value' => [
760 'description' => __( 'Meta value.', 'storeengine' ),
761 'type' => 'mixed',
762 'context' => [ 'view', 'edit' ],
763 ],
764 ],
765 ],
766 ],
767 ],
768 ];
769
770 return $this->add_additional_fields_schema( $schema );
771 }
772
773 /**
774 * Get the query params for collections.
775 *
776 * @return array
777 */
778 public function get_collection_params(): array {
779 $params = parent::get_collection_params();
780
781 $params['context']['default'] = 'view';
782
783 $params['exclude'] = [ // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
784 'description' => __( 'Ensure result set excludes specific IDs.', 'storeengine' ),
785 'type' => 'array',
786 'items' => [
787 'type' => 'integer',
788 ],
789 'default' => [],
790 'sanitize_callback' => 'wp_parse_id_list',
791 ];
792 $params['include'] = [
793 'description' => __( 'Limit result set to specific IDs.', 'storeengine' ),
794 'type' => 'array',
795 'items' => [
796 'type' => 'integer',
797 ],
798 'default' => [],
799 'sanitize_callback' => 'wp_parse_id_list',
800 ];
801 $params['offset'] = [
802 'description' => __( 'Offset the result set by a specific number of items.', 'storeengine' ),
803 'type' => 'integer',
804 'sanitize_callback' => 'absint',
805 'validate_callback' => 'rest_validate_request_arg',
806 ];
807 $params['order'] = [
808 'default' => 'desc',
809 'description' => __( 'Order sort attribute ascending or descending.', 'storeengine' ),
810 'enum' => [ 'asc', 'desc' ],
811 'sanitize_callback' => 'sanitize_key',
812 'type' => 'string',
813 'validate_callback' => 'rest_validate_request_arg',
814 ];
815 $params['orderby'] = [
816 'default' => 'id',
817 'description' => __( 'Sort collection by object attribute.', 'storeengine' ),
818 'enum' => [
819 'id',
820 'include',
821 'name',
822 'registered_date',
823 ],
824 'sanitize_callback' => 'sanitize_key',
825 'type' => 'string',
826 'validate_callback' => 'rest_validate_request_arg',
827 ];
828 $params['email'] = [
829 'description' => __( 'Limit result set to resources with a specific email.', 'storeengine' ),
830 'type' => 'string',
831 'format' => 'email',
832 'validate_callback' => 'rest_validate_request_arg',
833 ];
834
835 return $params;
836 }
837 }
838