PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.19
Booktics – Appointment Booking Calendar for Service Businesses v1.0.19
1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 All 27 releases
booktics / core / customer / controllers / customer-controller.php

customer-controller.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.19, at core/customer/controllers/customer-controller.php

661 lines 22.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Customer\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Abstracts\Booktics_Database;
7 use Booktics\Abstracts\User_Model;
8 use Booktics\Customer\Handlers\Customer_Event_Handler;
9 use Booktics\Models\Appointment_Model;
10 use Booktics\Models\Customer_Model;
11 use Booktics\Models\Guest_Model;
12 use Booktics\Models\Order_Model;
13 use Booktics\Models\Payment_Model;
14 use WP_Error;
15 use WP_HTTP_Response;
16 use WP_REST_Response;
17 use WP_REST_Server;
18
19 /**
20 * Customer controller
21 *
22 * @package Booktics/Customer
23 */
24 class Customer_Controller extends Base_Rest_Controller {
25
26 /**
27 * Store namespace of api
28 *
29 * @var string
30 */
31 protected $namespace = 'booktics/v1';
32
33 /**
34 * Store base url
35 *
36 * @var string
37 */
38 protected $base = 'customers';
39
40 /**
41 *
42 *
43 * @return void
44 */
45 public function register_routes(): void {
46 register_rest_route(
47 $this->namespace, $this->base, array(
48 array(
49 'methods' => WP_REST_Server::CREATABLE,
50 'callback' => array( $this, 'create_item' ),
51 'permission_callback' => array(
52 $this,
53 'create_customer_permission',
54 ),
55 ),
56 array(
57 'methods' => WP_REST_Server::READABLE,
58 'callback' => array( $this, 'get_items' ),
59 'permission_callback' => array(
60 $this,
61 'get_customer_permission',
62 ),
63 ),
64 array(
65 'methods' => WP_REST_Server::DELETABLE,
66 'callback' => array( $this, 'delete_items' ),
67 'permission_callback' => array(
68 $this,
69 'delete_customer_permission',
70 ),
71 ),
72 )
73 );
74
75 register_rest_route(
76 $this->namespace, $this->base . '/profile', array(
77 array(
78 'methods' => WP_REST_Server::READABLE,
79 'callback' => array( $this, 'get_profile' ),
80 'permission_callback' => array(
81 $this,
82 'customer_profile_permission',
83 ),
84 ),
85 )
86 );
87
88 register_rest_route(
89 $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)', array(
90 array(
91 'methods' => WP_REST_Server::EDITABLE,
92 'callback' => array( $this, 'update_item' ),
93 'permission_callback' => array(
94 $this,
95 'update_customer_permission',
96 ),
97 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
98 ),
99 array(
100 'methods' => WP_REST_Server::DELETABLE,
101 'callback' => array( $this, 'delete_item' ),
102 'permission_callback' => array(
103 $this,
104 'delete_customer_permission',
105 ),
106 ),
107 array(
108 'method' => WP_REST_Server::READABLE,
109 'callback' => array( $this, 'get_item' ),
110 'permission_callback' => array(
111 $this,
112 'get_customer_permission',
113 ),
114 ),
115 )
116 );
117
118 register_rest_route(
119 $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)/connect', array(
120 array(
121 'methods' => WP_REST_Server::EDITABLE,
122 'callback' => array( $this, 'connect_customer' ),
123 'permission_callback' => array(
124 $this,
125 'update_customer_permission',
126 ),
127 ),
128 )
129 );
130
131 register_rest_route(
132 $this->namespace, '/' . $this->base . '/(?P<id>[\d]+)/disconnect', array(
133 array(
134 'methods' => WP_REST_Server::EDITABLE,
135 'callback' => array( $this, 'disconnect_customer' ),
136 'permission_callback' => array(
137 $this,
138 'update_customer_permission',
139 ),
140 ),
141 )
142 );
143 }
144
145 /**
146 * Check if a given request has access to create items.
147 *
148 * @param $request Full data about the request.
149 */
150 public function create_customer_permission( $request ) {
151 return current_user_can( 'manage_options' );
152 }
153
154 /**
155 * Prepare and validate customer data
156 *
157 * @param $request
158 *
159 * @return array|bool|WP_Error
160 */
161 protected function prepare_customer_data( $request ) {
162 $input_data = json_decode( $request->get_body(), true );
163
164 $validate = booktics_validate(
165 $input_data, array(
166 'name' => array(
167 'required',
168 ),
169 'email' => array(
170 'required',
171 'unique:' . booktics_get_table_name( 'booktics_guests' ) . ',email',
172 ),
173 )
174 );
175
176 if ( is_wp_error( $validate ) ) {
177 return $validate;
178 }
179
180 return array(
181 'name' => $input_data['name'],
182 'email' => $input_data['email'],
183 'user_login' => User_Model::generate_username( $input_data['email'] ),
184 'phone' => $input_data['phone'],
185 );
186 }
187
188 /**
189 * Store new customer data
190 *
191 * @param $request
192 *
193 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
194 */
195 public function create_item( $request ) {
196 $prepared_data = $this->prepare_customer_data( $request );
197 if ( is_wp_error( $prepared_data ) ) {
198 return $this->error(
199 $prepared_data->get_error_message(),
200 422,
201 'Validation',
202 $prepared_data->errors
203 );
204 }
205
206 $db = new Booktics_Database();
207 $customer = ( new Guest_Model( $db ) )->create( $prepared_data );
208 $image_url = $request->get_param( 'image' );
209 if ( $image_url ) {
210 $customer->update( array( 'image' => esc_url_raw( $image_url ) ) );
211 }
212
213 if ( ! $customer ) {
214 return $this->error( __( 'Customer create error', 'booktics' ), 500 );
215 }
216
217 do_action( 'booktics_customer_created', $customer, $request );
218
219 return $this->response( $customer, __( 'Successfully created customer', 'booktics' ), 201 );
220 }
221
222 /**
223 * Check if a given request has access to get all customer
224 *
225 * @param $request
226 *
227 * @return bool
228 */
229 public function get_customer_permission( $request ) {
230 if ( current_user_can( 'manage_options' ) ) {
231 return true;
232 }
233
234 if ( is_user_logged_in() ) {
235 $current_user = wp_get_current_user();
236
237 if ( in_array( 'booktics_team_member', (array) $current_user->roles, true ) ) {
238 return true;
239 }
240
241 $requested_customer_id = isset( $request['id'] ) ? intval( $request['id'] ) : 0;
242
243 if ( $requested_customer_id > 0 ) {
244 $db = new Booktics_Database();
245 $requested_customer = ( new Guest_Model( $db ) )->find( array( 'id' => $requested_customer_id ) );
246
247 if ( $requested_customer && intval( $requested_customer->wp_user_id ) === get_current_user_id() ) {
248 return true;
249 }
250 }
251 }
252
253 return new WP_Error(
254 'booktics_forbidden',
255 __( 'You are not allowed to view this customer data.', 'booktics' ),
256 array( 'status' => 403 )
257 );
258 }
259
260 /**
261 * Get paginated list items for customer
262 *
263 * @param $request
264 *
265 * @return WP_Error|WP_HTTP_Response|WP_REST_Response
266 */
267 public function get_items( $request ) {
268 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 20;
269 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
270 $name = ! empty( $request['name'] ) ? sanitize_text_field( $request['name'] ) : '';
271 $email = ! empty( $request['email'] ) ? sanitize_email( $request['email'] ) : '';
272 $phone = ! empty( $request['phone'] ) ? sanitize_text_field( $request['phone'] ) : '';
273 $user_name = ! empty( $request['user_name'] ) ? sanitize_text_field( $request['user_name'] ) : '';
274 $search = ! empty( $request['search'] ) ? sanitize_text_field( $request['search'] ) : '';
275
276 $db = new Booktics_Database();
277 $customers = ( new Guest_Model( $db ) )->query();
278
279 $args = array(
280 'name' => array( 'LIKE', $name ),
281 'email' => array( 'LIKE', $email ),
282 'phone' => array( 'LIKE', $phone ),
283 'user_login' => array( 'LIKE', $user_name ),
284 );
285
286 foreach ( $args as $key => $value ) {
287 if ( $value[1] ) {
288 $customers->where( $key, $value[0], $value[1] );
289 }
290 }
291
292 if ( $search ) {
293 $customers->where( 'name', 'LIKE', $search )
294 ->or_where( 'email', 'LIKE', $search )
295 ->or_where( 'phone', 'LIKE', $search );
296 }
297
298 $customers = $customers->paginate( $paged, $per_page );
299 foreach ( $customers['items'] as &$customer ) {
300 $customer = ( new Guest_Model( $db ) )->with_virtual_attributes( $customer );
301
302 // Mask wp_user_id and user_login from non-admins (use empty string instead of unset for API consistency)
303 if ( ! current_user_can( 'manage_options' ) ) {
304 $customer->wp_user_id = '';
305 $customer->user_login = '';
306 }
307 }
308
309 return $this->response( $customers, __( 'Successfully fetched customer', 'booktics' ) );
310 }
311
312 /**
313 * Checks if a given request has access to delete a team member.
314 *
315 * @param $request
316 *
317 * @return bool
318 */
319 public function delete_customer_permission( $request ) {
320 return current_user_can( 'manage_options' );
321 }
322
323 /**
324 * Delete a customer
325 *
326 * @param $request
327 *
328 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
329 */
330 public function delete_item( $request ) {
331 $id = intval( $request['id'] );
332
333 $db = new Booktics_Database();
334 $order_model = new Order_Model( $db );
335 $orders = $order_model->get( array( 'customer_id' => $id ) );
336 foreach ( $orders['items'] as $order ) {
337 $appointment_model = new Appointment_Model();
338 $appointments = $appointment_model->where( 'order_id', $order->id );
339 foreach ( $appointments as $appointment ) {
340 $appointment->delete();
341 }
342 $payment_model = new Payment_Model( $db );
343 $payments = $payment_model->get( array( 'order_id' => $order->id ) );
344 foreach ( $payments['items'] as $payment ) {
345 ( new Payment_Model( $db ) )->delete( $payment->id );
346 }
347 $order_model->delete( $order->id );
348 }
349
350 $guest_model = ( new Guest_Model( $db ) );
351 $guest = $guest_model->find( array( 'id' => $id ) );
352
353 if ( $guest->wp_user_id ) {
354 $customer = new Customer_Model( $guest->wp_user_id );
355 $customer->delete();
356 }
357
358 $deleted = $guest_model->delete( $id );
359 if ( ! $deleted ) {
360 return $this->error( __( 'Customer delete error', 'booktics' ), 500 );
361 }
362
363 do_action( 'booktics_customers_deleted', $request );
364
365 return $this->response( array(), __( 'Successfully deleted customer', 'booktics' ) );
366 }
367
368 /**
369 * Delete multiple customer
370 *
371 * @param $request
372 *
373 * @return array|WP_Error|WP_HTTP_Response
374 */
375 public function delete_items( $request ) {
376 $ids = ! empty( $request['ids'] ) ? $request['ids'] : array();
377
378 if ( ! $ids ) {
379 return $this->error( __( 'Customer ids cannot be empty', 'booktics' ), 500 );
380 }
381 $count = 0;
382 $db = new Booktics_Database();
383 foreach ( $ids as $id ) {
384 // Delete all orders (and their related appointments and payments) for this customer
385 $order_model = new Order_Model( $db );
386 $orders = $order_model->get( array( 'customer_id' => $id ) );
387 foreach ( $orders['items'] as $order ) {
388 // Delete related appointments
389 $appointment_model = new Appointment_Model();
390 $appointments = $appointment_model->where( 'order_id', $order->id );
391 foreach ( $appointments as $appointment ) {
392 $appointment->delete();
393 }
394 // Delete related payments
395 $payment_model = new Payment_Model( $db );
396 $payments = $payment_model->get( array( 'order_id' => $order->id ) );
397 foreach ( $payments['items'] as $payment ) {
398 ( new Payment_Model( $db ) )->delete( $payment->id );
399 }
400 // Delete the order
401 $order_model->delete( $order->id );
402 }
403 $guest_model = ( new Guest_Model( $db ) );
404 $guest = $guest_model->find( array( 'id' => $id ) );
405 if ( $guest->wp_user_id ) {
406 $customer = new Customer_Model( $guest->wp_user_id );
407 $customer->delete();
408 }
409 if ( $guest_model->delete( $id ) ) {
410 ++$count;
411 }
412 }
413 if ( 0 == $count ) {
414 return $this->error( __( 'Customer delete error', 'booktics' ), 500 );
415 }
416
417 do_action( 'booktics_customers_deleted', $request );
418
419 return $this->response( array(), __( 'Successfully deleted customer', 'booktics' ) );
420 }
421
422 /**
423 * Get single customer
424 *
425 * @param $request
426 *
427 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
428 */
429 public function get_item( $request ) {
430 $id = intval( $request['id'] );
431 $db = new Booktics_Database();
432 $customer = ( new Guest_Model( $db ) )->find( array( 'id' => $id ) );
433 if ( ! $customer ) {
434 return $this->error( __( 'Customer not found', 'booktics' ), 404 );
435 }
436
437 $orders = ( new Order_Model( $db ) )->get( array( 'customer_id' => $id ) );
438 $customer->orders = array_map(
439 function ( $order ) {
440 $booking_count = ( new Appointment_Model() )
441 ->get_appointments_count_by_order_id( $order->id );
442
443 return array(
444 'date' => $order->created_at ? gmdate( 'j F, Y', strtotime( $order->created_at ) ) : '',
445 'total_booking' => $booking_count,
446 'total_amount' => $order->total,
447 );
448 }, $orders['items']
449 );
450
451 // Mask wp_user_id and user_login from non-admins (use empty string instead of unset for API consistency)
452 if ( ! current_user_can( 'manage_options' ) ) {
453 $customer->wp_user_id = '';
454 $customer->user_login = '';
455 }
456
457 return $this->response( $customer, __( 'Customer found successfully', 'booktics' ) );
458 }
459
460 /**
461 * Checks if a given request has access to update a customer.
462 *
463 * @param $request
464 *
465 * @return bool True if the request has access to update the item, WP_Error object otherwise.
466 */
467 public function update_customer_permission( $request ) {
468 return current_user_can( 'manage_options' );
469 }
470
471 /**
472 * Prepare data for customer update
473 *
474 * @param $request
475 *
476 * @return array|bool|WP_Error
477 */
478 public function prepare_customer_update( $request ) {
479 $input_data = json_decode( $request->get_body(), true );
480
481 $validation_rules = array(
482 'first_name' => array(
483 'required',
484 ),
485 'email' => array(
486 'required',
487 'unique:' . booktics_get_table_name( 'booktics_guests' ) . ',email,' . $request['id'],
488 ),
489 );
490 $update_validation_rules = array();
491 $validated_data = array();
492
493 if ( ! is_array( $input_data ) ) {
494 return new WP_Error(
495 'invalid_input_data',
496 'Invalid data'
497 );
498 }
499
500 foreach ( $input_data as $key => $value ) {
501 $validated_data[ $key ] = $value;
502 }
503
504 $validate = booktics_validate( $input_data, $update_validation_rules );
505
506 if ( is_wp_error( $validate ) ) {
507 return $validate;
508 }
509
510 return $validated_data;
511 }
512
513 /**
514 * @param $request
515 *
516 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
517 */
518 public function update_item( $request ) {
519 if ( ! isset( $request['id'] ) ) {
520 return $this->error( 'Invalid customer id', 400 );
521 }
522 $prepared_data = $this->prepare_customer_update( $request );
523
524 if ( is_wp_error( $prepared_data ) ) {
525 return $this->error( __( 'Customer validation error', 'booktics' ), 422, 'Validation', $prepared_data->errors );
526 }
527
528 $db = new Booktics_Database();
529 $customer = ( new Guest_Model( $db ) )->update( $request['id'], $prepared_data );
530 $image_url = $request->get_param( 'image' );
531 if ( $image_url ) {
532 ( new Guest_Model( $db ) )->update( $request['id'], array( 'image' => esc_url_raw( $image_url ) ) );
533 ( new Guest_Model( $db ) )->update( absint( $request['id'] ), array( 'image' => esc_url_raw( $image_url ) ) );
534 }
535
536 if ( ! $customer ) {
537 return $this->error( __( 'Customer update error', 'booktics' ), 500 );
538 }
539
540 do_action( 'booktics_customer_updated', $customer, $request );
541 $customer = ( new Guest_Model( $db ) )->find( array( 'id' => $request['id'] ) );
542
543 return $this->response( $customer, __( 'Successfully updated customer', 'booktics' ) );
544 }
545
546 /**
547 * Check if a given request has access to get customer profile.
548 *
549 * @param $request
550 *
551 * @return bool
552 */
553 public function customer_profile_permission( $request ) {
554 if ( is_user_logged_in() ) {
555 return true;
556 }
557
558 return new WP_Error(
559 'booktics_unauthorized',
560 __( 'You must be logged in to view your profile.', 'booktics' ),
561 array( 'status' => 401 )
562 );
563 }
564
565 /**
566 * Get customer profile
567 *
568 * @param $request
569 *
570 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
571 */
572 public function get_profile( $request ) {
573 $user_id = get_current_user_id();
574 $db = new Booktics_Database();
575 $customer = ( new Guest_Model( $db ) )->find( array( 'wp_user_id' => $user_id ) );
576 $args = array(
577 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Meta query is necessary for filtering customers by meta fields
578 'meta_query' => array(
579 array(
580 'key' => 'customer_id',
581 'value' => $customer->id,
582 'compare' => '=',
583 ),
584 ),
585 );
586
587 $appointments = ( new Appointment_Model() )->all( $args );
588 $customer->appointments = $appointments;
589
590 return $this->response( $customer, __( 'Successfully fetched customer profile', 'booktics' ) );
591 }
592
593 /**
594 * Connect a guest to a customer
595 *
596 * @param $request
597 *
598 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
599 */
600 public function connect_customer( $request ) {
601 $id = intval( $request['id'] );
602 $db = new Booktics_Database();
603 $guest = ( new Guest_Model( $db ) )->find( array( 'id' => $id ) );
604
605 if ( ! $guest ) {
606 return $this->error( __( 'Customer not found', 'booktics' ), 404 );
607 }
608
609 if ( $guest->wp_user_id ) {
610 return $this->error( __( 'Customer already connected', 'booktics' ), 400 );
611 }
612
613 $customer_model = ( new Customer_Model() );
614 $customer_id = $customer_model->save(
615 array(
616 'user_login' => $guest->user_login,
617 'user_email' => $guest->email,
618 'first_name' => $guest->first_name ?? '',
619 'last_name' => $guest->last_name ?? '',
620 'display_name' => $guest->display_name ?? '',
621 'phone' => $guest->phone ?? '',
622 )
623 );
624
625 if ( is_wp_error( $customer_id ) ) {
626 return $this->error( __( 'Customer create error', 'booktics' ), 500 );
627 }
628 ( new Customer_Event_Handler() )->on_customer_create( $customer_id );
629
630 ( new Guest_Model( $db ) )->update( $id, array( 'wp_user_id' => $customer_id ) );
631 $guest = ( new Guest_Model( $db ) )->find( array( 'id' => $id ) );
632 return $this->response( $guest, __( 'Successfully connected customer', 'booktics' ) );
633 }
634
635
636 /**
637 * Disconnect a guest from a customer
638 *
639 * @param $request
640 *
641 * @return array|WP_Error|WP_HTTP_Response|WP_REST_Response
642 */
643 public function disconnect_customer( $request ) {
644 $id = intval( $request['id'] );
645 $db = new Booktics_Database();
646 $guest = ( new Guest_Model( $db ) )->find( array( 'id' => $id ) );
647
648 if ( ! $guest ) {
649 return $this->error( __( 'Customer not found', 'booktics' ), 404 );
650 }
651 if ( $guest->wp_user_id ) {
652 $customer = new Customer_Model( $guest->wp_user_id );
653 $customer->delete();
654 }
655
656 ( new Guest_Model( $db ) )->update( $id, array( 'wp_user_id' => null ) );
657 $guest = ( new Guest_Model( $db ) )->find( array( 'id' => $id ) );
658 return $this->response( $guest, __( 'Successfully disconnected customer', 'booktics' ) );
659 }
660 }
661