PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.1
Booktics – Appointment Booking Calendar for Service Businesses v1.0.1
1.0.29 1.0.28 1.0.27 1.0.26 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 All 31 releases
booktics / core / appointment / controllers / appointment-controller.php

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

1,012 lines 37.5 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_HTTP_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 $search = ! empty( $request['search'] ) ? sanitize_text_field( $request['search'] ) : null;
230 $args = array( 'meta_query' => array() );
231
232 if ( $search ) {
233 $service_ids = [];
234 $customer_ids = [];
235 $team_member_ids = [];
236 // Search Services
237 $services = Service_Model::all( [
238 's' => $search,
239 ] );
240 $service_ids = array_map( function ( $service ) {
241 return $service->id;
242 }, $services );
243
244 // Search Customers
245 $customer_query = new \WP_User_Query( [
246 'search' => '*' . esc_attr( $search ) . '*',
247 'search_columns' => [ 'display_name', 'user_email' ],
248 'role' => 'booktics_customer',
249 'fields' => 'ID'
250 ] );
251 $customer_ids = $customer_query->get_results();
252
253 // Search Team Members
254 $team_member_query = new \WP_User_Query( [
255 'search' => '*' . esc_attr( $search ) . '*',
256 'search_columns' => [ 'display_name', 'user_email' ],
257 'role' => 'booktics_team_member',
258 'fields' => 'ID'
259 ] );
260 $team_member_ids = $team_member_query->get_results();
261
262 if ( $service_ids || $customer_ids || $team_member_ids ) {
263 $args['meta_query'] = [ 'relation' => 'OR' ];
264 if ( $service_ids ) {
265 $args['meta_query'][] = [
266 'key' => 'service_id',
267 'value' => $service_ids,
268 'compare' => 'IN'
269 ];
270 }
271 if ( $customer_ids ) {
272 $args['meta_query'][] = [
273 'key' => 'customer_id',
274 'value' => $customer_ids,
275 'compare' => 'IN'
276 ];
277 }
278 if ( $team_member_ids ) {
279 $args['meta_query'][] = [
280 'key' => 'team_member_id',
281 'value' => $team_member_ids,
282 'compare' => 'IN'
283 ];
284 }
285 } else {
286 return $this->response( [
287 'items' => [],
288 'total' => 0,
289 'per_page' => $per_page,
290 'current_page' => $paged,
291 'last_page' => 0,
292 ], __( 'No appointments found', 'booktics' ) );
293 }
294 }
295
296 if ( $date_from ) {
297 $args['meta_query'][] = array(
298 'key' => 'date',
299 'value' => $date_from,
300 'compare' => '>=',
301 'type' => 'DATE',
302 );
303 }
304
305 if ( $service_id ) {
306 $args['meta_query'][] = array(
307 'key' => 'service_id',
308 'value' => $service_id,
309 'compare' => '=',
310 );
311 }
312
313 if ( $customer_id ) {
314 $args['meta_query'][] = array(
315 'key' => 'customer_id',
316 'value' => $customer_id,
317 'compare' => '=',
318 );
319 }
320
321 if ( $payment_status ) {
322 $args['meta_query'][] = array(
323 'key' => 'payment_status',
324 'value' => $payment_status,
325 'compare' => '=',
326 );
327 }
328
329 if ( $team_member_id ) {
330 $args['meta_query'][] = array(
331 'key' => 'team_member_id',
332 'value' => $team_member_id,
333 'compare' => '=',
334 );
335 }
336
337 if ( $status ) {
338 $args['meta_query'][] = array(
339 'key' => 'status',
340 'value' => $status,
341 'compare' => '=',
342 );
343 }
344
345 $data = Appointment_Model::paginate( $per_page, $paged, $args );
346
347 if ( ! $data ) {
348 return $this->error( __( 'No appointment found', 'booktics' ), 404 );
349 }
350
351 return $this->response( $data );
352 }
353
354 /**
355 * Get one item from the collection.
356 *
357 * @param WP_REST_Request $request Full data about the request.
358 *
359 * @return WP_Error|WP_REST_Response
360 */
361 public function get_item( $request ) {
362 $id = intval( $request['id'] );
363
364 $appointment = Appointment_Model::find( $id );
365
366 if ( ! $appointment ) {
367 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
368 }
369
370 return $this->response( $appointment );
371 }
372
373 /**
374 * Get all calendar items within a date range
375 *
376 * @param WP_REST_Request $request Full data about the request.
377 *
378 * @return WP_Error|WP_REST_Response
379 */
380 public function get_all_calendar_items( $request ) {
381 $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 500;
382 $paged = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
383
384 // Check if custom date range is provided
385 if ( ! empty( $request['start_date'] ) && ! empty( $request['end_date'] ) ) {
386 $adjusted_start_date = sanitize_text_field( $request['start_date'] );
387 $adjusted_end_date = sanitize_text_field( $request['end_date'] );
388 $month = date( 'Y-m', strtotime( $adjusted_start_date ) );
389 } else {
390 // Fall back to month-based date range with week adjustment
391 $month = ! empty( $request['month'] ) ? sanitize_text_field( $request['month'] ) : gmdate( 'Y-m' );
392 $start_date = gmdate( 'Y-m-01', strtotime( $month ) );
393 $end_date = gmdate( 'Y-m-t', strtotime( $month ) );
394
395 // Get the first day of the week (Sunday = 0, Monday = 1, etc.)
396 $first_day_of_week = get_option( 'start_of_week', 1 );
397
398 // Adjust start date to include days from previous month to complete the first week
399 $start_date_obj = new \DateTime( $start_date );
400 $start_week_day = (int) $start_date_obj->format( 'w' ); // 0 (Sun) to 6 (Sat)
401 $days_to_subtract = ( $start_week_day - $first_day_of_week + 7 ) % 7;
402 $start_date_obj->modify( "-$days_to_subtract days" );
403 $adjusted_start_date = $start_date_obj->format( 'Y-m-d' );
404
405 // Adjust end date to include days from next month to complete the last week
406 $end_date_obj = new \DateTime( $end_date );
407 $end_week_day = (int) $end_date_obj->format( 'w' ); // 0 (Sun) to 6 (Sat)
408 $days_to_add = ( 6 - $end_week_day + $first_day_of_week ) % 7;
409 $end_date_obj->modify( "+$days_to_add days" );
410 $adjusted_end_date = $end_date_obj->format( 'Y-m-d' );
411 }
412
413 // Get all appointments within the date range using meta_query
414 $args = array(
415 'meta_query' => array(
416 'relation' => 'AND',
417 array(
418 'key' => 'date',
419 'value' => array(
420 $adjusted_start_date,
421 $adjusted_end_date
422 ),
423 'compare' => 'BETWEEN',
424 'type' => 'DATE',
425 ),
426 ),
427 'posts_per_page' => - 1, // Get all appointments in the range
428 'orderby' => 'meta_value',
429 'meta_key' => 'date',
430 'order' => 'ASC',
431 );
432
433 $service_id = ! empty( $request['service_id'] ) ? intval( $request['service_id'] ) : null;
434 $customer_id = ! empty( $request['customer_id'] ) ? intval( $request['customer_id'] ) : null;
435 $payment_status = ! empty( $request['payment_status'] ) ? sanitize_text_field( $request['payment_status'] ) : null;
436 $team_member_id = ! empty( $request['team_member_id'] ) ? intval( $request['team_member_id'] ) : null;
437 $status = ! empty( $request['status'] ) ? sanitize_text_field( $request['status'] ) : null;
438
439 if ( $service_id ) {
440 $args['meta_query'][] = array(
441 'key' => 'service_id',
442 'value' => $service_id,
443 'compare' => '=',
444 );
445 }
446
447 if ( $customer_id ) {
448 $args['meta_query'][] = array(
449 'key' => 'customer_id',
450 'value' => $customer_id,
451 'compare' => '=',
452 );
453 }
454
455 if ( $payment_status ) {
456 $args['meta_query'][] = array(
457 'key' => 'payment_status',
458 'value' => $payment_status,
459 'compare' => '=',
460 );
461 }
462
463 if ( $team_member_id ) {
464 $args['meta_query'][] = array(
465 'key' => 'team_member_id',
466 'value' => $team_member_id,
467 'compare' => '=',
468 );
469 }
470
471 if ( $status ) {
472 $args['meta_query'][] = array(
473 'key' => 'status',
474 'value' => $status,
475 'compare' => '=',
476 );
477 }
478
479 $appointments = Appointment_Model::paginate( $per_page, $paged, $args );
480
481 // Format the response
482 $formatted_appointments = array();
483
484 if ( ! empty( $appointments ) ) {
485 foreach ( $appointments['items'] as $appointment ) {
486 if ( ! empty( $appointment ) ) {
487 $appointment->source = 'booktics';
488 }
489
490 $formatted_appointments[] = $appointment;
491 }
492 }
493
494 // Allow other developers to add/modify appointments
495 $formatted_appointments = apply_filters(
496 'booktics_appointments_for_calendar_view',
497 $formatted_appointments,
498 array(
499 'start_date' => $adjusted_start_date,
500 'end_date' => $adjusted_end_date,
501 )
502 );
503
504 $response = array(
505 'month' => gmdate( 'Y-m', strtotime( $month ) ),
506 'date_range' => array(
507 'start' => $adjusted_start_date,
508 'end' => $adjusted_end_date,
509 ),
510 'appointments' => $formatted_appointments,
511 );
512
513 return $this->response( $response, __( 'Calendar appointments retrieved successfully', 'booktics' ) );
514 }
515
516 /**
517 * Check if a given request has access to get a specific item.
518 *
519 * @param WP_REST_Request $request Full data about the request.
520 *
521 * @return WP_Error|boolean
522 */
523 public function get_item_permissions_check( $request ) {
524 return true;
525 }
526
527 /**
528 * Check if a given request has access to get items.
529 *
530 * @param WP_REST_Request $request Full data about the request.
531 *
532 * @return WP_Error|boolean
533 */
534 public function get_items_permissions_check( $request ) {
535 return true;
536 }
537
538 /**
539 * Create one item from the collection.
540 *
541 * @param WP_REST_Request $request Full data about the request.
542 *
543 * @return WP_Error|WP_REST_Response
544 * @throws Exception
545 */
546 public function create_item( $request ) {
547 $data = $this->prepare_item_for_database( $request );
548
549 if ( is_wp_error( $data ) ) {
550 return $this->error( $data->get_error_message() );
551 }
552
553 $appointment = Appointment_Model::create( $data );
554
555 if ( is_wp_error( $appointment ) ) {
556 return $this->error( $appointment->get_error_message() );
557 }
558
559 do_action( 'booktics_after_appointment_created', $appointment );
560
561 return $this->response( $appointment, __( 'Successfully created appointment', 'booktics' ) );
562 }
563
564 /**
565 * Check if a given request has access to create items.
566 *
567 * @param WP_REST_Request $request Full data about the request.
568 *
569 * @return WP_Error|boolean
570 */
571 public function create_item_permissions_check( $request ) {
572 return current_user_can( 'manage_options' );
573 }
574
575 /**
576 * Update one item from the collection.
577 *
578 * @param WP_REST_Request $request Full data about the request.
579 *
580 * @return WP_REST_Response
581 */
582 public function update_item( $request ) {
583 $id = intval( $request['id'] );
584 $data = json_decode( $request->get_body(), true );
585
586 if ( empty( $data ) || ! is_array( $data ) ) {
587 return $this->error( __( 'No data provided for update', 'booktics' ), 400 );
588 }
589
590 // Get the appointment to update
591 $appointment = Appointment_Model::find( $id );
592
593 if ( ! $appointment ) {
594 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
595 }
596
597 if ( isset( $data ['status'] ) ) {
598 if ( ! in_array( $data ['status'], Appointment_Model::get_accepted_statuses() ) ) {
599 return $this->error( __( 'Invalid status', 'booktics' ), 400 );
600 }
601 $appointment->status = $data ['status'];
602 }
603
604 if ( ! empty( $data['customer'] ) ) {
605 $customer = ( new Customer_Model() )->find( $appointment->customer_id );
606 if ( $customer ) {
607 $updated_customer = $customer->update( $data['customer'] );
608 unset( $data['customer'] );
609 }
610 }
611
612 $updated_appointment = $appointment->update( $data );
613
614 if ( isset( $updated_customer ) ) {
615 $updated_appointment->customer = $updated_customer;
616 }
617
618 if ( is_wp_error( $updated_appointment ) ) {
619 return $this->error( $updated_appointment->get_error_message() );
620 }
621
622 do_action( 'booktics_after_appointment_updated', $appointment );
623
624 return $this->response( $updated_appointment, __( 'Appointment updated successfully', 'booktics' ) );
625 }
626
627 /**
628 * Check if a given request has access to update a specific item.
629 *
630 * @param WP_REST_Request $request Full data about the request.
631 *
632 * @return WP_Error|boolean
633 */
634 public function update_item_permissions_check( $request ) {
635 return current_user_can( 'manage_options' );
636 }
637
638 /**
639 * Delete one item from the collection.
640 *
641 * @param WP_REST_Request $request Full data about the request.
642 *
643 * @return \WP_Error|\WP_HTTP_Response
644 */
645 public function delete_item( $request ) {
646 $id = intval( $request['id'] );
647
648 $appointment = Appointment_Model::find( $id );
649
650 if ( ! $appointment ) {
651 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
652 }
653
654 $deleted = $appointment->delete();
655
656 if ( is_wp_error( $deleted ) ) {
657 return $this->error( $deleted->get_error_message() );
658 }
659
660 do_action( 'booktics_after_appointment_deleted', $appointment );
661
662 return $this->response( $appointment, __( 'Successfully deleted appointment', 'booktics' ) );
663 }
664
665 /**
666 * Check if a given request has access to delete a specific item.
667 *
668 * @param WP_REST_Request $request Full data about the request.
669 *
670 * @return WP_Error|boolean
671 */
672 public function delete_item_permissions_check( $request ) {
673 return current_user_can( 'manage_options' );
674 }
675
676 /**
677 * Delete a collection of items
678 *
679 * @param WP_Rest_Request $request User request
680 */
681 public function delete_items( $request ) {
682 $ids = $request['ids'];
683
684 if ( ! is_array( $ids ) ) {
685 return $this->error( __( 'Ids should be an array of items ids', 'booktics' ) );
686 }
687
688 $count = 0;
689 $total = count( $ids );
690
691 foreach ( $ids as $id ) {
692 $appointment = Appointment_Model::find( $id );
693
694 if ( $appointment ) {
695 $appointment->delete();
696 ++ $count;
697 }
698 }
699
700 if ( $count === 0 ) {
701 return $this->error( __( 'No item deleted.', 'booktics' ) );
702 }
703
704 $message = sprintf( 'Deleted %d items of %d', $count, $total );
705
706 return $this->response( array(), $message );
707 }
708
709 /**
710 * Check if a given request has access to delete a collection of items.
711 *
712 * @param WP_REST_Request $request Full data about the request.
713 *
714 * @return WP_Error|boolean
715 */
716 public function delete_items_permissions_check( $request ) {
717 return current_user_can( 'manage_options' );
718 }
719
720 /**
721 * Reschedule an appointment
722 *
723 * @param $request Full data about the request.
724 *
725 * @throws Exception
726 */
727 public function reschedule_item( $request ) {
728 $id = intval( $request['id'] );
729 $data = json_decode( $request->get_body(), true );
730
731 $validate = booktics_validate(
732 $data,
733 array(
734 'start_time' => array( 'required' ),
735 'end_time' => array( 'required' ),
736 'date' => array( 'required' ),
737 )
738 );
739
740 if ( is_wp_error( $validate ) ) {
741 return $this->error( $validate->get_error_message() );
742 }
743
744 $appointment = Appointment_Model::find( $id );
745 if ( ! $appointment ) {
746 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
747 }
748
749 $db = new Booktics_Database();
750 $service_schedules = $db->table( 'booktics_schedules' )
751 ->where( 'service_id', '=', $appointment->service_id )
752 ->where( 'custom_date', '=', '0' )
753 ->to_array();
754 $service_schedules = new Service_Schedule( $service_schedules, $appointment->service_id );
755
756 $team_member_schedules = $db->table( 'booktics_schedules' )
757 ->where( 'team_member_id', '=', $appointment->team_member_id )
758 ->where( 'custom_date', '=', '0' )
759 ->to_array();
760 $team_member_schedules = new Team_Member_Schedule( $team_member_schedules, $appointment->team_member_id );
761
762 $default_schedules = $db->table( 'booktics_schedules' )
763 ->where( 'service_id', '=', 0 )
764 ->where( 'team_member_id', '=', 0 )
765 ->where( 'custom_date', '=', '0' )
766 ->to_array();
767 $default_schedules = new Default_Schedule( $default_schedules );
768
769 $slots = ( new Time_Slot_Manager( $service_schedules, $team_member_schedules, $default_schedules ) )
770 ->generate_slots_for_range( $data['date'], $data['date'] );
771
772 if ( is_wp_error( $validate ) ) {
773 return $this->error( $validate->get_error_message() );
774 }
775
776 $updated = $appointment->update(
777 array(
778 'start_time' => sanitize_text_field( $data['start_time'] ),
779 'end_time' => sanitize_text_field( $data['end_time'] ),
780 'date' => sanitize_text_field( $data['date'] ),
781 )
782 );
783
784 if ( is_wp_error( $updated ) ) {
785 return $this->error( __( 'Appointment reschedule error.', 'booktics' ) );
786 }
787
788 $service = Service_Model::find( $appointment->service_id );
789 $team_member = ( new Team_Member_Model() )->find( $appointment->team_member_id );
790 $customer = ( new Customer_Model() )->find( $appointment->customer_id );
791 do_action( 'booktics_after_appointment_rescheduled', $appointment );
792 do_action( 'global_notification_hook', 'appointment_rescheduled', [
793 'appointment_service' => $service->title,
794 'date' => $appointment->date,
795 'appointment_team_member' => $team_member->display_name,
796 'start_time' => $appointment->start_time,
797 'end_time' => $appointment->end_time,
798 'user_email' => $customer->user_email,
799 ] );
800
801 return $this->response( $appointment, __( 'Successfully rescheduled appointment', 'booktics' ) );
802 }
803
804 /**
805 * Check if a given request has access to reschedule a specific item.
806 *
807 * @param $request Full data about the request.
808 */
809 public function reschedule_item_permissions_check( $request ) {
810 return true;
811 }
812
813 /**
814 * Cancel an appointment
815 *
816 * @param WP_REST_Request $request Full data about the request.
817 *
818 * @throws Exception
819 */
820 public function cancel_item( $request ) {
821 $id = intval( $request['id'] );
822
823 $appointment = Appointment_Model::find( $id );
824
825 if ( ! $appointment ) {
826 return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
827 }
828 $updated = $appointment->update( array( 'status' => 'cancelled' ) );
829
830 if ( is_wp_error( $updated ) ) {
831 return $this->error( __( 'Appointment cancelletion error.', 'booktics' ) );
832 }
833 $service = Service_Model::find( $appointment->service_id );
834 $team_member = ( new Team_Member_Model() )->find( $appointment->team_member_id );
835 $customer = ( new Customer_Model() )->find( $appointment->customer_id );
836
837 do_action( 'booktics_after_appointment_cancelled', $appointment );
838 do_action( 'global_notification_hook', 'appointment_cancelled', [
839 'appointment_service' => $service->title,
840 'date' => $appointment->date,
841 'appointment_team_member' => $team_member->display_name,
842 'start_time' => $appointment->start_time,
843 'end_time' => $appointment->end_time,
844 'user_email' => $customer->user_email,
845 ] );
846
847 return $this->response( $appointment, __( 'Successfully cancelled appointment', 'booktics' ) );
848 }
849
850 /**
851 * Check if a given request has access to cancel a specific item.
852 *
853 * @param WP_REST_Request $request Full data about the request.
854 *
855 * @return WP_Error|boolean
856 */
857 public function cancel_item_permissions_check( $request ) {
858 return current_user_can( 'manage_options' );
859 }
860
861 /**
862 * Prepare the item for create or update operation.
863 *
864 * @param WP_REST_Request $request Request object.
865 *
866 * @return bool|\WP_Error $prepared_item
867 */
868 public function prepare_item_for_database( $request ) {
869 $data = json_decode( $request->get_body(), true );
870 $validate = booktics_validate(
871 $data,
872 array(
873 'start_time' => array(
874 'required',
875 ),
876 'end_time' => array(
877 'required',
878 ),
879 'date' => array( 'required' ),
880 'service_id' => array(
881 'required',
882 ),
883 'team_member_id' => array(
884 'required',
885 ),
886 )
887 );
888
889 if ( is_wp_error( $validate ) ) {
890 return $validate;
891 }
892
893 $data = $this->get_sanitized_data( $data );
894
895 return $data;
896 }
897
898 private function get_sanitized_data( $data ) {
899 $data['start_time'] = ! empty( $data['start_time'] ) ? sanitize_text_field( $data['start_time'] ) : '';
900 $data['end_time'] = ! empty( $data['end_time'] ) ? sanitize_text_field( $data['end_time'] ) : '';
901 $data['date'] = ! empty( $data['date'] ) ? sanitize_text_field( $data['date'] ) : '';
902 $data['service_id'] = ! empty( $data['service_id'] ) ? sanitize_text_field( $data['service_id'] ) : '';
903 $data['team_member_id'] = ! empty( $data['team_member_id'] ) ? sanitize_text_field( $data['team_member_id'] ) : '';
904 $data['customer_id'] = ! empty( $data['customer_id'] ) ? sanitize_text_field( $data['customer_id'] ) : '';
905 $data['total_attendees'] = ! empty( $data['total_attendees'] ) ? sanitize_text_field( $data['total_attendees'] ) : '';
906 $data['status'] = isset( $data['status'] ) ? $data['status'] : 'pending';
907
908 return $data;
909 }
910
911 /**
912 * Check if a given request has access to get time slots.
913 *
914 * @param $request
915 *
916 * @return boolean
917 */
918 public function get_time_slots_permissions_check( $request ) {
919 return true;
920 }
921
922 /**
923 * Fetch appointment time slots
924 *
925 * @param WP_REST_Request $request Request object.
926 *
927 * @return array|\WP_HTTP_Response
928 * @throws Exception If there's an error getting time slots.
929 */
930 public function get_time_slots( $request ) {
931 $service_id = $request['service_id'];
932 $additional_service_id = $request['additional_service_id'];
933 $team_member_id = $request['team_member_id'];
934 $start_date = $request['start_date'];
935 $end_date = $request['end_date'];
936
937 $db = new Booktics_Database();
938
939 $service_schedules = $db->table( 'booktics_schedules' )
940 ->where( 'service_id', '=', $service_id )
941 ->where_group(
942 function ( $q ) use ( $start_date, $end_date ) {
943 $q->where( 'custom_date', '=', '0' )
944 ->or_where_group(
945 function ( $q2 ) use ( $start_date, $end_date ) {
946 $q2->where( 'custom_date', '>', $start_date )
947 ->where( 'custom_date', '<', $end_date );
948 }
949 );
950 }
951 )
952 ->to_array();
953 $service_schedules = new Service_Schedule( $service_schedules, $service_id );
954
955 $team_member_schedules = $db->table( 'booktics_schedules' )
956 ->where( 'team_member_id', '=', $team_member_id )
957 ->where_group(
958 function ( $q ) use ( $start_date, $end_date ) {
959 $q->where( 'custom_date', '=', '0' )
960 ->or_where_group(
961 function ( $q2 ) use ( $start_date, $end_date ) {
962 $q2->where( 'custom_date', '>', $start_date )
963 ->where( 'custom_date', '<', $end_date );
964 }
965 );
966 }
967 )
968 ->to_array();
969 $team_member_schedules = new Team_Member_Schedule( $team_member_schedules, $team_member_id );
970
971 $default_schedules = $db->table( 'booktics_schedules' )
972 ->where( 'service_id', '=', 0 )
973 ->where( 'team_member_id', '=', 0 )
974 ->where_group(
975 function ( $q ) use ( $start_date, $end_date ) {
976 $q->where( 'custom_date', '=', '0' )
977 ->or_where_group(
978 function ( $q2 ) use ( $start_date, $end_date ) {
979 $q2->where( 'custom_date', '>', $start_date )
980 ->where( 'custom_date', '<', $end_date );
981 }
982 );
983 }
984 )
985 ->to_array();
986 $default_schedules = new Default_Schedule( $default_schedules );
987
988 $service = Service_Model::find( $service_id );
989 $service_additional_services = ! empty( $service->additional_durations )
990 ? array_filter(
991 $service->additional_durations,
992 function ( $duration ) use ( $additional_service_id ) {
993 return $duration['id'] === $additional_service_id;
994 }
995 ) : array();
996 $service_duration = ! empty( $service_additional_services ) ? reset( $service_additional_services )['duration_time'] : 30;
997
998 $slots = ( new Time_Slot_Manager( $service_schedules, $team_member_schedules, $default_schedules, $service_duration ) )
999 ->generate_slots_for_range( $start_date, $end_date );
1000 $response = array(
1001 'service_id' => $service_id,
1002 'team_member_id' => $team_member_id,
1003 'additional_service_id' => $additional_service_id,
1004 'start_date' => $start_date,
1005 'end_date' => $end_date,
1006 'slots' => $slots,
1007 );
1008
1009 return $this->response( $response, __( 'Successfully fetched time slots', 'booktics' ) );
1010 }
1011 }
1012