PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.0
Booktics – Appointment Booking Calendar for Service Businesses v1.0.0
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 / appointment / controllers / appointment-controller.php

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

900 lines 32.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Booktics\Appointment\Controllers;
4
5 use Booktics\Abstracts\Base_Rest_Controller;
6 use Booktics\Abstracts\Booktics_Database;
7 use Booktics\Appointment\Schedule\Default_Schedule;
8 use Booktics\Appointment\Schedule\Service_Schedule;
9 use Booktics\Appointment\Schedule\Team_Member_Schedule;
10 use Booktics\Appointment\Schedule\Time_Slot_Manager;
11 use Booktics\Models\Appointment_Model;
12 use Booktics\Models\Customer_Model;
13 use Booktics\Models\Service_Model;
14 use Booktics\Models\Team_Member_Model;
15 use Exception;
16 use WP_Error;
17 use WP_HTTP_Response;
18 use WP_REST_Request;
19 use WP_REST_Response;
20 use WP_REST_Server;
21
22 /**
23 * Appointment controller
24 *
25 * @package Booktics/Appointment
26 */
27 class Appointment_Controller extends Base_Rest_Controller {
28 /**
29 * Endpoint namespace
30 *
31 * @var string
32 */
33 protected $namespace = 'booktics/v1';
34
35 /**
36 * Route name
37 *
38 * @var string
39 */
40 protected $base = 'appointments';
41
42 /**
43 * Register routes
44 *
45 * @return void
46 */
47 public function register_routes(): void {
48 register_rest_route(
49 $this->namespace,
50 $this->base,
51 array(
52 array(
53 'methods' => WP_REST_Server::CREATABLE,
54 'callback' => array( $this, 'create_item' ),
55 'permission_callback' => array(
56 $this,
57 'create_item_permissions_check',
58 ),
59 ),
60 )
61 );
62
63 register_rest_route(
64 $this->namespace,
65 $this->base,
66 array(
67 array(
68 'methods' => WP_REST_Server::READABLE,
69 'callback' => array( $this, 'get_items' ),
70 'permission_callback' => array(
71 $this,
72 'get_items_permissions_check',
73 ),
74 ),
75 )
76 );
77
78 register_rest_route(
79 $this->namespace,
80 $this->base,
81 array(
82 array(
83 'methods' => WP_REST_Server::DELETABLE,
84 'callback' => array( $this, 'delete_items' ),
85 'permission_callback' => array(
86 $this,
87 'delete_items_permissions_check',
88 ),
89 ),
90 )
91 );
92
93 register_rest_route(
94 $this->namespace,
95 $this->base . '/(?P<id>[\d]+)',
96 array(
97 array(
98 'methods' => WP_REST_Server::READABLE,
99 'callback' => array( $this, 'get_item' ),
100 'permission_callback' => array(
101 $this,
102 'get_item_permissions_check',
103 ),
104 ),
105 )
106 );
107
108 register_rest_route(
109 $this->namespace,
110 $this->base . '/(?P<id>[\d]+)',
111 array(
112 array(
113 'methods' => WP_REST_Server::EDITABLE,
114 'callback' => array( $this, 'update_item' ),
115 'permission_callback' => array(
116 $this,
117 'update_item_permissions_check',
118 ),
119 ),
120 )
121 );
122
123 register_rest_route(
124 $this->namespace,
125 $this->base . '/(?P<id>[\d]+)',
126 array(
127 array(
128 'methods' => WP_REST_Server::DELETABLE,
129 'callback' => array( $this, 'delete_item' ),
130 'permission_callback' => array(
131 $this,
132 'delete_item_permissions_check',
133 ),
134 ),
135 )
136 );
137
138 register_rest_route(
139 $this->namespace,
140 $this->base . '/(?P<id>[\d]+)/reschedule',
141 array(
142 array(
143 'methods' => WP_REST_Server::EDITABLE,
144 'callback' => array( $this, 'reschedule_item' ),
145 'permission_callback' => array(
146 $this,
147 'reschedule_item_permissions_check',
148 ),
149 ),
150 )
151 );
152
153 register_rest_route(
154 $this->namespace,
155 $this->base . '/(?P<id>[\d]+)/cancel',
156 array(
157 array(
158 'methods' => WP_REST_Server::EDITABLE,
159 'callback' => array( $this, 'cancel_item' ),
160 'permission_callback' => array(
161 $this,
162 'cancel_item_permissions_check',
163 ),
164 ),
165 )
166 );
167
168 register_rest_route(
169 $this->namespace,
170 $this->base . '/time-slots',
171 array(
172 array(
173 'methods' => WP_REST_Server::READABLE,
174 'callback' => array( $this, 'get_time_slots' ),
175 'permission_callback' => array(
176 $this,
177 'get_time_slots_permissions_check',
178 ),
179 ),
180 )
181 );
182
183 register_rest_route(
184 $this->namespace,
185 $this->base . '/calendar',
186 array(
187 array(
188 'methods' => WP_REST_Server::READABLE,
189 'callback' => array(
190 $this,
191 'get_all_calendar_items'
192 ),
193 'permission_callback' => array(
194 $this,
195 'get_items_permissions_check'
196 ),
197 'args' => array(
198 'month' => array(
199 'description' => __( 'Month in YYYY-MM format', 'booktics' ),
200 'type' => 'string',
201 'validate_callback' => function ( $param ) {
202 return preg_match( '/^\d{4}-\d{2}$/', $param );
203 },
204 ),
205 ),
206 ),
207 )
208 );
209 }
210
211 /**
212 * Get a collection of items.
213 *
214 * @param WP_REST_Request $request Full data about the request.
215 *
216 * @return WP_Error|WP_REST_Response
217 */
218 public function get_items( $request ) {
219 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 10;
220 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
221 $date_from = ! empty( $request['date_from'] ) ? sanitize_text_field( $request['date_from'] ) : null;
222
223 $service_id = ! empty( $request['service_id'] ) ? intval( $request['service_id'] ) : null;
224 $customer_id = ! empty( $request['customer_id'] ) ? intval( $request['customer_id'] ) : null;
225 $payment_status = ! empty( $request['payment_status'] ) ? sanitize_text_field( $request['payment_status'] ) : null;
226 $team_member_id = ! empty( $request['team_member_id'] ) ? intval( $request['team_member_id'] ) : null;
227 $status = ! empty( $request['status'] ) ? sanitize_text_field( $request['status'] ) : null;
228
229 $args = array( 'meta_query' => array() );
230 if ( $date_from ) {
231 $args['meta_query'][] = array(
232 'key' => 'date',
233 'value' => $date_from,
234 'compare' => '>=',
235 'type' => 'DATE',
236 );
237 }
238
239 if ( $service_id ) {
240 $args['meta_query'][] = array(
241 'key' => 'service_id',
242 'value' => $service_id,
243 'compare' => '=',
244 );
245 }
246
247 if ( $customer_id ) {
248 $args['meta_query'][] = array(
249 'key' => 'customer_id',
250 'value' => $customer_id,
251 'compare' => '=',
252 );
253 }
254
255 if ( $payment_status ) {
256 $args['meta_query'][] = array(
257 'key' => 'payment_status',
258 'value' => $payment_status,
259 'compare' => '=',
260 );
261 }
262
263 if ( $team_member_id ) {
264 $args['meta_query'][] = array(
265 'key' => 'team_member_id',
266 'value' => $team_member_id,
267 'compare' => '=',
268 );
269 }
270
271 if ( $status ) {
272 $args['meta_query'][] = array(
273 'key' => 'status',
274 'value' => $status,
275 'compare' => '=',
276 );
277 }
278
279 $data = Appointment_Model::paginate( $per_page, $paged, $args );
280
281 if ( ! $data ) {
282 return $this->error( __( 'No appointment found', 'booktics' ), 404 );
283 }
284
285 return $this->response( $data );
286 }
287
288 /**
289 * Get one item from the collection.
290 *
291 * @param WP_REST_Request $request Full data about the request.
292 *
293 * @return WP_Error|WP_REST_Response
294 */
295 public function get_item( $request ) {
296 $id = intval( $request['id'] );
297
298 $appointment = Appointment_Model::find( $id );
299
300 if ( ! $appointment ) {
301 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
302 }
303
304 return $this->response( $appointment );
305 }
306
307 /**
308 * Get all calendar items within a date range
309 *
310 * @param WP_REST_Request $request Full data about the request.
311 *
312 * @return WP_Error|WP_REST_Response
313 */
314 public function get_all_calendar_items( $request ) {
315 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 500;
316 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
317
318 // Check if custom date range is provided
319 if ( ! empty( $request['start_date'] ) && ! empty( $request['end_date'] ) ) {
320 $adjusted_start_date = sanitize_text_field( $request['start_date'] );
321 $adjusted_end_date = sanitize_text_field( $request['end_date'] );
322 $month = date( 'Y-m', strtotime( $adjusted_start_date ) );
323 } else {
324 // Fall back to month-based date range with week adjustment
325 $month = ! empty( $request['month'] ) ? sanitize_text_field( $request['month'] ) : gmdate( 'Y-m' );
326 $start_date = gmdate( 'Y-m-01', strtotime( $month ) );
327 $end_date = gmdate( 'Y-m-t', strtotime( $month ) );
328
329 // Get the first day of the week (Sunday = 0, Monday = 1, etc.)
330 $first_day_of_week = get_option( 'start_of_week', 1 );
331
332 // Adjust start date to include days from previous month to complete the first week
333 $start_date_obj = new \DateTime( $start_date );
334 $start_week_day = (int) $start_date_obj->format( 'w' ); // 0 (Sun) to 6 (Sat)
335 $days_to_subtract = ( $start_week_day - $first_day_of_week + 7 ) % 7;
336 $start_date_obj->modify( "-$days_to_subtract days" );
337 $adjusted_start_date = $start_date_obj->format( 'Y-m-d' );
338
339 // Adjust end date to include days from next month to complete the last week
340 $end_date_obj = new \DateTime( $end_date );
341 $end_week_day = (int) $end_date_obj->format( 'w' ); // 0 (Sun) to 6 (Sat)
342 $days_to_add = ( 6 - $end_week_day + $first_day_of_week ) % 7;
343 $end_date_obj->modify( "+$days_to_add days" );
344 $adjusted_end_date = $end_date_obj->format( 'Y-m-d' );
345 }
346
347 // Get all appointments within the date range using meta_query
348 $args = array(
349 'meta_query' => array(
350 'relation' => 'AND',
351 array(
352 'key' => 'date',
353 'value' => array(
354 $adjusted_start_date,
355 $adjusted_end_date
356 ),
357 'compare' => 'BETWEEN',
358 'type' => 'DATE',
359 ),
360 ),
361 'posts_per_page' => - 1, // Get all appointments in the range
362 'orderby' => 'meta_value',
363 'meta_key' => 'date',
364 'order' => 'ASC',
365 );
366
367 $appointments = Appointment_Model::paginate( $per_page, $paged, $args );
368
369 // Format the response
370 $formatted_appointments = array();
371
372 if ( ! empty( $appointments ) ) {
373 foreach ( $appointments['items'] as $appointment ) {
374 if ( ! empty( $appointment ) ) {
375 $appointment->source = 'booktics';
376 }
377
378 $formatted_appointments[] = $appointment;
379 }
380 }
381
382 // Allow other developers to add/modify appointments
383 $formatted_appointments = apply_filters(
384 'booktics_appointments_for_calendar_view',
385 $formatted_appointments,
386 array(
387 'start_date' => $adjusted_start_date,
388 'end_date' => $adjusted_end_date,
389 )
390 );
391
392 $response = array(
393 'month' => gmdate( 'Y-m', strtotime( $month ) ),
394 'date_range' => array(
395 'start' => $adjusted_start_date,
396 'end' => $adjusted_end_date,
397 ),
398 'appointments' => $formatted_appointments,
399 );
400
401 return $this->response( $response, __( 'Calendar appointments retrieved successfully', 'booktics' ) );
402 }
403
404 /**
405 * Check if a given request has access to get a specific item.
406 *
407 * @param WP_REST_Request $request Full data about the request.
408 *
409 * @return WP_Error|boolean
410 */
411 public function get_item_permissions_check( $request ) {
412 return true;
413 }
414
415 /**
416 * Check if a given request has access to get items.
417 *
418 * @param WP_REST_Request $request Full data about the request.
419 *
420 * @return WP_Error|boolean
421 */
422 public function get_items_permissions_check( $request ) {
423 return true;
424 }
425
426 /**
427 * Create one item from the collection.
428 *
429 * @param WP_REST_Request $request Full data about the request.
430 *
431 * @return WP_Error|WP_REST_Response
432 * @throws Exception
433 */
434 public function create_item( $request ) {
435 $data = $this->prepare_item_for_database( $request );
436
437 if ( is_wp_error( $data ) ) {
438 return $this->error( $data->get_error_message() );
439 }
440
441 $appointment = Appointment_Model::create( $data );
442
443 if ( is_wp_error( $appointment ) ) {
444 return $this->error( $appointment->get_error_message() );
445 }
446
447 do_action( 'booktics_after_appointment_created', $appointment );
448
449 return $this->response( $appointment, __( 'Successfully created appointment', 'booktics' ) );
450 }
451
452 /**
453 * Check if a given request has access to create items.
454 *
455 * @param WP_REST_Request $request Full data about the request.
456 *
457 * @return WP_Error|boolean
458 */
459 public function create_item_permissions_check( $request ) {
460 return current_user_can( 'manage_options' );
461 }
462
463 /**
464 * Update one item from the collection.
465 *
466 * @param WP_REST_Request $request Full data about the request.
467 *
468 * @return WP_REST_Response
469 */
470 public function update_item( $request ) {
471 $id = intval( $request['id'] );
472 $data = json_decode( $request->get_body(), true );
473
474 if ( empty( $data ) || ! is_array( $data ) ) {
475 return $this->error( __( 'No data provided for update', 'booktics' ), 400 );
476 }
477
478 // Get the appointment to update
479 $appointment = Appointment_Model::find( $id );
480
481 if ( ! $appointment ) {
482 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
483 }
484
485 if ( isset( $data ['status'] ) ) {
486 if ( ! in_array( $data ['status'], Appointment_Model::get_accepted_statuses() ) ) {
487 return $this->error( __( 'Invalid status', 'booktics' ), 400 );
488 }
489 $appointment->status = $data ['status'];
490 }
491
492 if ( ! empty( $data['customer'] ) ) {
493 $customer = ( new Customer_Model() )->find( $appointment->customer_id );
494 if ( $customer ) {
495 $updated_customer = $customer->update( $data['customer'] );
496 unset( $data['customer'] );
497 }
498 }
499
500 $updated_appointment = $appointment->update( $data );
501
502 if ( isset( $updated_customer ) ) {
503 $updated_appointment->customer = $updated_customer;
504 }
505
506 if ( is_wp_error( $updated_appointment ) ) {
507 return $this->error( $updated_appointment->get_error_message() );
508 }
509
510 do_action( 'booktics_after_appointment_updated', $appointment );
511
512 return $this->response( $updated_appointment, __( 'Appointment updated successfully', 'booktics' ) );
513 }
514
515 /**
516 * Check if a given request has access to update a specific item.
517 *
518 * @param WP_REST_Request $request Full data about the request.
519 *
520 * @return WP_Error|boolean
521 */
522 public function update_item_permissions_check( $request ) {
523 return current_user_can( 'manage_options' );
524 }
525
526 /**
527 * Delete one item from the collection.
528 *
529 * @param WP_REST_Request $request Full data about the request.
530 *
531 * @return \WP_Error|\WP_HTTP_Response
532 */
533 public function delete_item( $request ) {
534 $id = intval( $request['id'] );
535
536 $appointment = Appointment_Model::find( $id );
537
538 if ( ! $appointment ) {
539 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
540 }
541
542 $deleted = $appointment->delete();
543
544 if ( is_wp_error( $deleted ) ) {
545 return $this->error( $deleted->get_error_message() );
546 }
547
548 do_action( 'booktics_after_appointment_deleted', $appointment );
549
550 return $this->response( $appointment, __( 'Successfully deleted appointment', 'booktics' ) );
551 }
552
553 /**
554 * Check if a given request has access to delete a specific item.
555 *
556 * @param WP_REST_Request $request Full data about the request.
557 *
558 * @return WP_Error|boolean
559 */
560 public function delete_item_permissions_check( $request ) {
561 return current_user_can( 'manage_options' );
562 }
563
564 /**
565 * Delete a collection of items
566 *
567 * @param WP_Rest_Request $request User request
568 */
569 public function delete_items( $request ) {
570 $ids = $request['ids'];
571
572 if ( ! is_array( $ids ) ) {
573 return $this->error( __( 'Ids should be an array of items ids', 'booktics' ) );
574 }
575
576 $count = 0;
577 $total = count( $ids );
578
579 foreach ( $ids as $id ) {
580 $appointment = Appointment_Model::find( $id );
581
582 if ( $appointment ) {
583 $appointment->delete();
584 ++ $count;
585 }
586 }
587
588 if ( $count === 0 ) {
589 return $this->error( __( 'No item deleted.', 'booktics' ) );
590 }
591
592 $message = sprintf( 'Deleted %d items of %d', $count, $total );
593
594 return $this->response( array(), $message );
595 }
596
597 /**
598 * Check if a given request has access to delete a collection of items.
599 *
600 * @param WP_REST_Request $request Full data about the request.
601 *
602 * @return WP_Error|boolean
603 */
604 public function delete_items_permissions_check( $request ) {
605 return current_user_can( 'manage_options' );
606 }
607
608 /**
609 * Reschedule an appointment
610 *
611 * @param $request Full data about the request.
612 *
613 * @throws Exception
614 */
615 public function reschedule_item( $request ) {
616 $id = intval( $request['id'] );
617 $data = json_decode( $request->get_body(), true );
618
619 $validate = booktics_validate(
620 $data,
621 array(
622 'start_time' => array( 'required' ),
623 'end_time' => array( 'required' ),
624 'date' => array( 'required' ),
625 )
626 );
627
628 if ( is_wp_error( $validate ) ) {
629 return $this->error( $validate->get_error_message() );
630 }
631
632 $appointment = Appointment_Model::find( $id );
633 if ( ! $appointment ) {
634 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
635 }
636
637 $db = new Booktics_Database();
638 $service_schedules = $db->table( 'booktics_schedules' )
639 ->where( 'service_id', '=', $appointment->service_id )
640 ->where( 'custom_date', '=', '0' )
641 ->to_array();
642 $service_schedules = new Service_Schedule( $service_schedules, $appointment->service_id );
643
644 $team_member_schedules = $db->table( 'booktics_schedules' )
645 ->where( 'team_member_id', '=', $appointment->team_member_id )
646 ->where( 'custom_date', '=', '0' )
647 ->to_array();
648 $team_member_schedules = new Team_Member_Schedule( $team_member_schedules, $appointment->team_member_id );
649
650 $default_schedules = $db->table( 'booktics_schedules' )
651 ->where( 'service_id', '=', 0 )
652 ->where( 'team_member_id', '=', 0 )
653 ->where( 'custom_date', '=', '0' )
654 ->to_array();
655 $default_schedules = new Default_Schedule( $default_schedules );
656
657 $slots = ( new Time_Slot_Manager( $service_schedules, $team_member_schedules, $default_schedules ) )
658 ->generate_slots_for_range( $data['date'], $data['date'] );
659
660 if ( is_wp_error( $validate ) ) {
661 return $this->error( $validate->get_error_message() );
662 }
663
664 $updated = $appointment->update(
665 array(
666 'start_time' => sanitize_text_field( $data['start_time'] ),
667 'end_time' => sanitize_text_field( $data['end_time'] ),
668 'date' => sanitize_text_field( $data['date'] ),
669 )
670 );
671
672 if ( is_wp_error( $updated ) ) {
673 return $this->error( __( 'Appointment reschedule error.', 'booktics' ) );
674 }
675
676 $service = Service_Model::find( $appointment->service_id );
677 $team_member = ( new Team_Member_Model() )->find( $appointment->team_member_id );
678 $customer = ( new Customer_Model() )->find( $appointment->customer_id );
679 do_action( 'booktics_after_appointment_rescheduled', $appointment );
680 do_action( 'global_notification_hook', 'appointment_rescheduled', [
681 'appointment_service' => $service->title,
682 'date' => $appointment->date,
683 'appointment_team_member' => $team_member->display_name,
684 'start_time' => $appointment->start_time,
685 'end_time' => $appointment->end_time,
686 'user_email' => $customer->user_email,
687 ] );
688
689 return $this->response( $appointment, __( 'Successfully rescheduled appointment', 'booktics' ) );
690 }
691
692 /**
693 * Check if a given request has access to reschedule a specific item.
694 *
695 * @param $request Full data about the request.
696 */
697 public function reschedule_item_permissions_check( $request ) {
698 return true;
699 }
700
701 /**
702 * Cancel an appointment
703 *
704 * @param WP_REST_Request $request Full data about the request.
705 *
706 * @throws Exception
707 */
708 public function cancel_item( $request ) {
709 $id = intval( $request['id'] );
710
711 $appointment = Appointment_Model::find( $id );
712
713 if ( ! $appointment ) {
714 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
715 }
716 $updated = $appointment->update( array( 'status' => 'cancelled' ) );
717
718 if ( is_wp_error( $updated ) ) {
719 return $this->error( __( 'Appointment cancelletion error.', 'booktics' ) );
720 }
721 $service = Service_Model::find( $appointment->service_id );
722 $team_member = ( new Team_Member_Model() )->find( $appointment->team_member_id );
723 $customer = ( new Customer_Model() )->find( $appointment->customer_id );
724
725 do_action( 'booktics_after_appointment_cancelled', $appointment );
726 do_action( 'global_notification_hook', 'appointment_cancelled', [
727 'appointment_service' => $service->title,
728 'date' => $appointment->date,
729 'appointment_team_member' => $team_member->display_name,
730 'start_time' => $appointment->start_time,
731 'end_time' => $appointment->end_time,
732 'user_email' => $customer->user_email,
733 ] );
734
735 return $this->response( $appointment, __( 'Successfully cancelled appointment', 'booktics' ) );
736 }
737
738 /**
739 * Check if a given request has access to cancel a specific item.
740 *
741 * @param WP_REST_Request $request Full data about the request.
742 *
743 * @return WP_Error|boolean
744 */
745 public function cancel_item_permissions_check( $request ) {
746 return current_user_can( 'manage_options' );
747 }
748
749 /**
750 * Prepare the item for create or update operation.
751 *
752 * @param WP_REST_Request $request Request object.
753 *
754 * @return bool|\WP_Error $prepared_item
755 */
756 public function prepare_item_for_database( $request ) {
757 $data = json_decode( $request->get_body(), true );
758 $validate = booktics_validate(
759 $data,
760 array(
761 'start_time' => array(
762 'required',
763 ),
764 'end_time' => array(
765 'required',
766 ),
767 'date' => array( 'required' ),
768 'service_id' => array(
769 'required',
770 ),
771 'team_member_id' => array(
772 'required',
773 ),
774 )
775 );
776
777 if ( is_wp_error( $validate ) ) {
778 return $validate;
779 }
780
781 $data = $this->get_sanitized_data( $data );
782
783 return $data;
784 }
785
786 private function get_sanitized_data( $data ) {
787 $data['start_time'] = ! empty( $data['start_time'] ) ? sanitize_text_field( $data['start_time'] ) : '';
788 $data['end_time'] = ! empty( $data['end_time'] ) ? sanitize_text_field( $data['end_time'] ) : '';
789 $data['date'] = ! empty( $data['date'] ) ? sanitize_text_field( $data['date'] ) : '';
790 $data['service_id'] = ! empty( $data['service_id'] ) ? sanitize_text_field( $data['service_id'] ) : '';
791 $data['team_member_id'] = ! empty( $data['team_member_id'] ) ? sanitize_text_field( $data['team_member_id'] ) : '';
792 $data['customer_id'] = ! empty( $data['customer_id'] ) ? sanitize_text_field( $data['customer_id'] ) : '';
793 $data['total_attendees'] = ! empty( $data['total_attendees'] ) ? sanitize_text_field( $data['total_attendees'] ) : '';
794 $data['status'] = isset( $data['status'] ) ? $data['status'] : 'pending';
795
796 return $data;
797 }
798
799 /**
800 * Check if a given request has access to get time slots.
801 *
802 * @param $request
803 *
804 * @return boolean
805 */
806 public function get_time_slots_permissions_check( $request ) {
807 return true;
808 }
809
810 /**
811 * Fetch appointment time slots
812 *
813 * @param WP_REST_Request $request Request object.
814 *
815 * @return array|\WP_HTTP_Response
816 * @throws Exception If there's an error getting time slots.
817 */
818 public function get_time_slots( $request ) {
819 $service_id = $request['service_id'];
820 $additional_service_id = $request['additional_service_id'];
821 $team_member_id = $request['team_member_id'];
822 $start_date = $request['start_date'];
823 $end_date = $request['end_date'];
824
825 $db = new Booktics_Database();
826
827 $service_schedules = $db->table( 'booktics_schedules' )
828 ->where( 'service_id', '=', $service_id )
829 ->where_group(
830 function ( $q ) use ( $start_date, $end_date ) {
831 $q->where( 'custom_date', '=', '0' )
832 ->or_where_group(
833 function ( $q2 ) use ( $start_date, $end_date ) {
834 $q2->where( 'custom_date', '>', $start_date )
835 ->where( 'custom_date', '<', $end_date );
836 }
837 );
838 }
839 )
840 ->to_array();
841 $service_schedules = new Service_Schedule( $service_schedules, $service_id );
842
843 $team_member_schedules = $db->table( 'booktics_schedules' )
844 ->where( 'team_member_id', '=', $team_member_id )
845 ->where_group(
846 function ( $q ) use ( $start_date, $end_date ) {
847 $q->where( 'custom_date', '=', '0' )
848 ->or_where_group(
849 function ( $q2 ) use ( $start_date, $end_date ) {
850 $q2->where( 'custom_date', '>', $start_date )
851 ->where( 'custom_date', '<', $end_date );
852 }
853 );
854 }
855 )
856 ->to_array();
857 $team_member_schedules = new Team_Member_Schedule( $team_member_schedules, $team_member_id );
858
859 $default_schedules = $db->table( 'booktics_schedules' )
860 ->where( 'service_id', '=', 0 )
861 ->where( 'team_member_id', '=', 0 )
862 ->where_group(
863 function ( $q ) use ( $start_date, $end_date ) {
864 $q->where( 'custom_date', '=', '0' )
865 ->or_where_group(
866 function ( $q2 ) use ( $start_date, $end_date ) {
867 $q2->where( 'custom_date', '>', $start_date )
868 ->where( 'custom_date', '<', $end_date );
869 }
870 );
871 }
872 )
873 ->to_array();
874 $default_schedules = new Default_Schedule( $default_schedules );
875
876 $service = Service_Model::find( $service_id );
877 $service_additional_services = ! empty( $service->additional_durations )
878 ? array_filter(
879 $service->additional_durations,
880 function ( $duration ) use ( $additional_service_id ) {
881 return $duration['id'] === $additional_service_id;
882 }
883 ) : array();
884 $service_duration = ! empty( $service_additional_services ) ? reset( $service_additional_services )['duration_time'] : 30;
885
886 $slots = ( new Time_Slot_Manager( $service_schedules, $team_member_schedules, $default_schedules, $service_duration ) )
887 ->generate_slots_for_range( $start_date, $end_date );
888 $response = array(
889 'service_id' => $service_id,
890 'team_member_id' => $team_member_id,
891 'additional_service_id' => $additional_service_id,
892 'start_date' => $start_date,
893 'end_date' => $end_date,
894 'slots' => $slots,
895 );
896
897 return $this->response( $response, __( 'Successfully fetched time slots', 'booktics' ) );
898 }
899 }
900