# booktics/1.0.12/core/appointment/controllers/appointment-controller.php

Booktics – Appointment Booking Calendar for Service Businesses, version 1.0.12. 1,007 lines.

- Page: https://pluginprobe.com/plugins/booktics/1.0.12/code/core/appointment/controllers/appointment-controller.php
- Raw: https://pluginprobe.com/plugins/booktics/1.0.12/raw/core/appointment/controllers/appointment-controller.php
- Modified: 2025-12-18T06:51:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/booktics/1.0.12/code/core/appointment/controllers/appointment-controller.php#L10-L20`.

```php
<?php

namespace Booktics\Appointment\Controllers;

use Booktics\Abstracts\Base_Rest_Controller;
use Booktics\Abstracts\Booktics_Database;
use Booktics\Appointment\Schedule\Default_Schedule;
use Booktics\Appointment\Schedule\Service_Schedule;
use Booktics\Appointment\Schedule\Team_Member_Schedule;
use Booktics\Appointment\Schedule\Time_Slot_Manager;
use Booktics\Appointment\Services\Appointment_Service;
use Booktics\Appointment\Services\Appointment_Validator;
use Booktics\Models\Appointment_Model;
use Booktics\Models\Service_Model;
use Booktics\Models\Team_Member_Model;
use Booktics\Models\Guest_Model;
use Exception;
use WP_Error;
use WP_HTTP_Response;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Appointment controller
 *
 * @package Booktics/Appointment
 */
class Appointment_Controller extends Base_Rest_Controller {
    /**
     * Endpoint namespace
     *
     * @var string
     */
    protected $namespace = 'booktics/v1';

    /**
     * Route name
     *
     * @var string
     */
    protected $base = 'appointments';

    /**
     * Register routes
     *
     * @return  void
     */
    public function register_routes(): void {
        register_rest_route(
            $this->namespace,
            $this->base,
            array(
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array( $this, 'create_item' ),
                    'permission_callback' => array(
                        $this,
                        'create_item_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base,
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_items' ),
                    'permission_callback' => array(
                        $this,
                        'get_items_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base,
            array(
                array(
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => array( $this, 'delete_items' ),
                    'permission_callback' => array(
                        $this,
                        'delete_items_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/(?P<id>[\d]+)',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_item' ),
                    'permission_callback' => array(
                        $this,
                        'get_item_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/(?P<id>[\d]+)',
            array(
                array(
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => array( $this, 'update_item' ),
                    'permission_callback' => array(
                        $this,
                        'update_item_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/(?P<id>[\d]+)',
            array(
                array(
                    'methods'             => WP_REST_Server::DELETABLE,
                    'callback'            => array( $this, 'delete_item' ),
                    'permission_callback' => array(
                        $this,
                        'delete_item_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/(?P<id>[\d]+)/reschedule',
            array(
                array(
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => array( $this, 'reschedule_item' ),
                    'permission_callback' => array(
                        $this,
                        'reschedule_item_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/(?P<id>[\d]+)/cancel',
            array(
                array(
                    'methods'             => WP_REST_Server::EDITABLE,
                    'callback'            => array( $this, 'cancel_item' ),
                    'permission_callback' => array(
                        $this,
                        'cancel_item_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/time-slots',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array( $this, 'get_time_slots' ),
                    'permission_callback' => array(
                        $this,
                        'get_time_slots_permissions_check',
                    ),
                ),
            )
        );

        register_rest_route(
            $this->namespace,
            $this->base . '/calendar',
            array(
                array(
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => array(
                        $this,
                        'get_all_calendar_items',
                    ),
                    'permission_callback' => array(
                        $this,
                        'get_items_permissions_check',
                    ),
                    'args'                => array(
                        'month' => array(
                            'description'       => __( 'Month in YYYY-MM format', 'booktics' ),
                            'type'              => 'string',
                            'validate_callback' => function ( $param ) {
                                return preg_match( '/^\d{4}-\d{2}$/', $param );
                            },
                        ),
                    ),
                ),
            )
        );
    }

    /**
     * Get a collection of items.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_HTTP_Response
     */
    public function get_items( $request ) {
        $per_page  = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 10;
        $paged     = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;
        $date_from = ! empty( $request['date_from'] ) ? sanitize_text_field( $request['date_from'] ) : null;

        $service_id     = ! empty( $request['service_id'] ) ? intval( $request['service_id'] ) : null;
        $customer_id    = ! empty( $request['customer_id'] ) ? intval( $request['customer_id'] ) : null;
        $payment_status = ! empty( $request['payment_status'] ) ? sanitize_text_field( $request['payment_status'] ) : null;
        $team_member_id = ! empty( $request['team_member_id'] ) ? intval( $request['team_member_id'] ) : null;
        $status         = ! empty( $request['status'] ) ? sanitize_text_field( $request['status'] ) : null;

        $search = ! empty( $request['search'] ) ? sanitize_text_field( $request['search'] ) : null;
        $args   = array( 'meta_query' => array() );

        if ( $search ) {
            $service_ids     = array();
            $customer_ids    = array();
            $team_member_ids = array();
            // Search Services
            $services    = Service_Model::all(
                array(
					's' => $search,
                )
            );
            $service_ids = array_map(
                function ( $service ) {
                    return $service->id;
                }, $services
            );

            // Search Customers
            $customer_query = new \WP_User_Query(
                array(
					'search'         => '*' . esc_attr( $search ) . '*',
					'search_columns' => array( 'display_name', 'user_email' ),
					'role'           => 'booktics_customer',
					'fields'         => 'ID',
                )
            );
            $customer_ids   = $customer_query->get_results();

            // Search Team Members
            $team_member_query = new \WP_User_Query(
                array(
					'search'         => '*' . esc_attr( $search ) . '*',
					'search_columns' => array( 'display_name', 'user_email' ),
					'role'           => 'booktics_team_member',
					'fields'         => 'ID',
                )
            );
            $team_member_ids   = $team_member_query->get_results();

            if ( $service_ids || $customer_ids || $team_member_ids ) {
                $args['meta_query'] = array( 'relation' => 'OR' );
                if ( $service_ids ) {
                    $args['meta_query'][] = array(
                        'key'     => 'service_id',
                        'value'   => $service_ids,
                        'compare' => 'IN',
                    );
                }
                if ( $customer_ids ) {
                    $args['meta_query'][] = array(
                        'key'     => 'customer_id',
                        'value'   => $customer_ids,
                        'compare' => 'IN',
                    );
                }
                if ( $team_member_ids ) {
                    $args['meta_query'][] = array(
                        'key'     => 'team_member_id',
                        'value'   => $team_member_ids,
                        'compare' => 'IN',
                    );
                }
            } else {
                return $this->response(
                    array(
						'items'        => array(),
						'total'        => 0,
						'per_page'     => $per_page,
						'current_page' => $paged,
						'last_page'    => 0,
                    ), __( 'No appointments found', 'booktics' )
                );
            }
        }

        if ( $date_from ) {
            $args['meta_query'][] = array(
                'key'     => 'date',
                'value'   => $date_from,
                'compare' => '>=',
                'type'    => 'DATE',
            );
        }

        if ( $service_id ) {
            $args['meta_query'][] = array(
                'key'     => 'service_id',
                'value'   => $service_id,
                'compare' => '=',
            );
        }

        if ( $customer_id ) {
            $args['meta_query'][] = array(
                'key'     => 'customer_id',
                'value'   => $customer_id,
                'compare' => '=',
            );
        }

        if ( $payment_status ) {
            $args['meta_query'][] = array(
                'key'     => 'payment_status',
                'value'   => $payment_status,
                'compare' => '=',
            );
        }

        if ( $team_member_id ) {
            $args['meta_query'][] = array(
                'key'     => 'team_member_id',
                'value'   => $team_member_id,
                'compare' => '=',
            );
        }

        // Adds filtering by appointment status
        if ( $status ) {
            $args['post_status'] = is_array( $status ) ? $status : array( $status );
        }

        $args = apply_filters( 'booktics_appointment_args', $args );

        $data = Appointment_Model::paginate( $per_page, $paged, $args );

        $data = apply_filters( 'booktics_appointment_data', $data );

        if ( ! $data ) {
            return $this->error( __( 'No appointment found', 'booktics' ), 404 );
        }

        return $this->response( $data );
    }

    /**
     * Get one item from the collection.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|WP_REST_Response
     */
    public function get_item( $request ) {
        $id = intval( $request['id'] );

        $appointment = Appointment_Model::find( $id );

        if ( ! $appointment ) {
            return $this->error( __( 'Appointment not found', 'booktics' ), 404 );
        }

        return $this->response( $appointment );
    }

    /**
     * Get all calendar items within a date range
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|WP_REST_Response
     */
    public function get_all_calendar_items( $request ) {
        $per_page = ! empty( $request['per_page'] ) ? intval( $request['per_page'] ) : 500;
        $paged    = ! empty( $request['paged'] ) ? intval( $request['paged'] ) : 1;

        // Check if custom date range is provided
        if ( ! empty( $request['start_date'] ) && ! empty( $request['end_date'] ) ) {
            $adjusted_start_date = sanitize_text_field( $request['start_date'] );
            $adjusted_end_date   = sanitize_text_field( $request['end_date'] );
            $month               = date( 'Y-m', strtotime( $adjusted_start_date ) );
        } else {
            // Fall back to month-based date range with week adjustment
            $month      = ! empty( $request['month'] ) ? sanitize_text_field( $request['month'] ) : gmdate( 'Y-m' );
            $start_date = gmdate( 'Y-m-01', strtotime( $month ) );
            $end_date   = gmdate( 'Y-m-t', strtotime( $month ) );

            // Get the first day of the week (Sunday = 0, Monday = 1, etc.)
            $first_day_of_week = get_option( 'start_of_week', 1 );

            // Adjust start date to include days from previous month to complete the first week
            $start_date_obj   = new \DateTime( $start_date );
            $start_week_day   = (int) $start_date_obj->format( 'w' ); // 0 (Sun) to 6 (Sat)
            $days_to_subtract = ( $start_week_day - $first_day_of_week + 7 ) % 7;
            $start_date_obj->modify( "-$days_to_subtract days" );
            $adjusted_start_date = $start_date_obj->format( 'Y-m-d' );

            // Adjust end date to include days from next month to complete the last week
            $end_date_obj = new \DateTime( $end_date );
            $end_week_day = (int) $end_date_obj->format( 'w' ); // 0 (Sun) to 6 (Sat)
            $days_to_add  = ( 6 - $end_week_day + $first_day_of_week ) % 7;
            $end_date_obj->modify( "+$days_to_add days" );
            $adjusted_end_date = $end_date_obj->format( 'Y-m-d' );
        }

        // Get all appointments within the date range using meta_query
        $args = array(
            'meta_query'     => array(
                'relation' => 'AND',
                array(
                    'key'     => 'date',
                    'value'   => array(
                        $adjusted_start_date,
                        $adjusted_end_date,
                    ),
                    'compare' => 'BETWEEN',
                    'type'    => 'DATE',
                ),
            ),
            'posts_per_page' => - 1, // Get all appointments in the range
            'orderby'        => 'meta_value',
            'meta_key'       => 'date',
            'order'          => 'ASC',
        );

        $service_id     = ! empty( $request['service_id'] ) ? intval( $request['service_id'] ) : null;
        $customer_id    = ! empty( $request['customer_id'] ) ? intval( $request['customer_id'] ) : null;
        $payment_status = ! empty( $request['payment_status'] ) ? sanitize_text_field( $request['payment_status'] ) : null;
        $team_member_id = ! empty( $request['team_member_id'] ) ? intval( $request['team_member_id'] ) : null;
        $status         = ! empty( $request['status'] ) ? sanitize_text_field( $request['status'] ) : null;

        if ( $service_id ) {
            $args['meta_query'][] = array(
                'key'     => 'service_id',
                'value'   => $service_id,
                'compare' => '=',
            );
        }

        if ( $customer_id ) {
            $args['meta_query'][] = array(
                'key'     => 'customer_id',
                'value'   => $customer_id,
                'compare' => '=',
            );
        }

        if ( $payment_status ) {
            $args['meta_query'][] = array(
                'key'     => 'payment_status',
                'value'   => $payment_status,
                'compare' => '=',
            );
        }

        if ( $team_member_id ) {
            $args['meta_query'][] = array(
                'key'     => 'team_member_id',
                'value'   => $team_member_id,
                'compare' => '=',
            );
        }

        if ( $status ) {
            $args['meta_query'][] = array(
                'key'     => 'status',
                'value'   => $status,
                'compare' => '=',
            );
        }

        $args = apply_filters( 'booktics_appointment_args', $args );

        $appointments = Appointment_Model::paginate( $per_page, $paged, $args );

        // Format the response
        $formatted_appointments = array();

        if ( ! empty( $appointments ) ) {
            foreach ( $appointments['items'] as $appointment ) {
                if ( ! empty( $appointment ) ) {
                    $appointment->source = 'booktics';
                }

                $formatted_appointments[] = $appointment;
            }
        }

        // Allow other developers to add/modify appointments
        $formatted_appointments = apply_filters(
            'booktics_appointments_for_calendar_view',
            $formatted_appointments,
            array(
                'start_date' => $adjusted_start_date,
                'end_date'   => $adjusted_end_date,
            )
        );

        $response = array(
            'month'        => gmdate( 'Y-m', strtotime( $month ) ),
            'date_range'   => array(
                'start' => $adjusted_start_date,
                'end'   => $adjusted_end_date,
            ),
            'appointments' => $formatted_appointments,
        );

        return $this->response( $response, __( 'Calendar appointments retrieved successfully', 'booktics' ) );
    }

    /**
     * Check if a given request has access to get a specific item.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function get_item_permissions_check( $request ) {
        return true;
    }

    /**
     * Check if a given request has access to get items.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function get_items_permissions_check( $request ) {
        return true;
    }

    /**
     * Create one item from the collection.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|WP_REST_Response
     * @throws Exception
     */
    public function create_item( $request ) {
        $data  = json_decode( $request->get_body(), true );
        $error = Appointment_Validator::instance()->validate_create( $data );
        if ( $error ) {
            return $this->error( $error->get_error_message() );
        }
        $appointment = Appointment_Service::instance()->create( $data );
        if ( is_wp_error( $appointment ) ) {
            return $this->error( $appointment->get_error_message() );
        }
        do_action( 'booktics_after_appointment_created', $appointment );

        return $this->response( $appointment, __( 'Successfully created appointment', 'booktics' ) );
    }

    /**
     * Check if a given request has access to create items.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function create_item_permissions_check( $request ) {
        return current_user_can( 'manage_options' );
    }

    /**
     * Update one item from the collection.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_REST_Response
     */
    public function update_item( $request ) {
        $id    = intval( $request['id'] );
        $data  = json_decode( $request->get_body(), true );
        $error = Appointment_Validator::instance()->validate_update( $data );
        if ( $error ) {
            return $this->error( $error->get_error_message() );
        }
        $updated_appointment = Appointment_Service::instance()->update( $id, $data );
        if ( is_wp_error( $updated_appointment ) ) {
            return $this->error( $updated_appointment->get_error_message() );
        }
        do_action( 'booktics_after_appointment_updated', $updated_appointment );

        return $this->response( $updated_appointment, __( 'Appointment updated successfully', 'booktics' ) );
    }

    /**
     * Check if a given request has access to update a specific item.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function update_item_permissions_check( $request ) {
        return current_user_can( 'manage_options' );
    }

    /**
     * Delete one item from the collection.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return \WP_Error|\WP_HTTP_Response
     */
    public function delete_item( $request ) {
        $id     = intval( $request['id'] );
        $result = Appointment_Service::instance()->delete( $id );
        if ( is_wp_error( $result ) ) {
            return $this->error( $result->get_error_message() );
        }
        do_action( 'booktics_after_appointment_deleted', $result );

        return $this->response( $result, __( 'Successfully deleted appointment', 'booktics' ) );
    }

    /**
     * Check if a given request has access to delete a specific item.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function delete_item_permissions_check( $request ) {
        return current_user_can( 'manage_options' );
    }

    /**
     * Delete a collection of items
     *
     * @param WP_Rest_Request $request User request
     */
    public function delete_items( $request ) {
        $ids    = $request['ids'];
        $result = Appointment_Service::instance()->delete_many( $ids );
        if ( is_wp_error( $result ) ) {
            return $this->error( $result->get_error_message() );
        }

        return $this->response( $result['data'], $result['message'] );
    }

    /**
     * Check if a given request has access to delete a collection of items.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function delete_items_permissions_check( $request ) {
        return current_user_can( 'manage_options' );
    }

    /**
     * Reschedule an appointment
     *
     * @param $request Full data about the request.
     *
     * @throws Exception
     */
    public function reschedule_item( $request ) {
        $id    = intval( $request['id'] );
        $data  = json_decode( $request->get_body(), true );
        $error = Appointment_Validator::instance()->validate_reschedule( $data, $id );
        if ( $error ) {
            return $this->error( $error->get_error_message() );
        }
        $result = Appointment_Service::instance()->reschedule( $id, $data );
        if ( is_wp_error( $result ) ) {
            return $this->error( $result->get_error_message() );
        }

        $this->send_reschedule_email( $result );

        do_action( 'booktics_after_appointment_rescheduled', $result );

        return $this->response( $result, __( 'Successfully rescheduled appointment', 'booktics' ) );
    }

    /**
     * Check if a given request has access to reschedule a specific item.
     *
     * @param $request Full data about the request.
     */
    public function reschedule_item_permissions_check( $request ) {
        return true;
    }

    /**
     * Send reschedule email to customer
     *
     * @param Appointment_Model $appointment Appointment model.
     */
    private function send_reschedule_email( $appointment ) {
        $db          = new Booktics_Database();
        $customer    = ( new Guest_Model( $db ) )->find( [ 'id' => $appointment->customer_id ] );
        $team_member = new Team_Member_Model( $appointment->team_member_id );

        $appointment_reschedule_email_data = [
            'date'          => $appointment->date ?? '',
            'start_time'    => $appointment->start_time ?? '',
            'end_time'      => $appointment->end_time ?? '',
            'customer_name' => $customer->name ?? '',
            'user_email'    => $customer->email ?? '',
            'appointment_service' => $appointment->title ?? '',
            'appointment_team_member' => $team_member->display_name ?? '',
            'appointment_team_member_email' => $team_member->user_email ?? '',  
        ];

        $appointment_reschedule_email_data = apply_filters( 'booktics_appointment_reschedule_email_data', $appointment_reschedule_email_data );

        do_action( 'global_notification_hook', 'appointment_rescheduled', $appointment_reschedule_email_data );
    }

    /**
     * Cancel an appointment
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @throws Exception
     */
    public function cancel_item( $request ) {
        $id     = intval( $request['id'] );
        $data   = json_decode( $request->get_body(), true );
        $cancel_note = ! empty( $data['cancel_note'] ) ? sanitize_text_field( $data['cancel_note'] ) : '';
        
        $error = Appointment_Validator::instance()->validate_cancel( $id );
        
        if ( $error ) {
            return $this->error( $error->get_error_message() );
        }

        $result = Appointment_Service::instance()->cancel( $id, [ 'cancel_note' => $cancel_note ] );

        if ( is_wp_error( $result ) ) {
            return $this->error( $result->get_error_message() );
        }

        $this->send_cancel_email( $result );

        do_action( 'booktics_after_appointment_cancelled', $result );

        return $this->response( $result, __( 'Successfully cancelled appointment', 'booktics' ) );
    }

    /**
     * Send cancel email to customer
     *
     * @param Appointment_Model $appointment Appointment model.
     */
    private function send_cancel_email( $appointment ) {
        $db          = new Booktics_Database();
        $customer    = ( new Guest_Model( $db ) )->find( [ 'id' => $appointment->customer_id ] );
        $team_member = new Team_Member_Model( $appointment->team_member_id );

        $appointment_cancel_email_data = [
            'date'          => $appointment->date ?? '',
            'start_time'    => $appointment->start_time ?? '',
            'end_time'      => $appointment->end_time ?? '',
            'customer_name' => $customer->name ?? '',
            'user_email'    => $customer->email ?? '',
            'appointment_service' => $appointment->title ?? '',
            'appointment_team_member' => $team_member->display_name ?? '',  
            'appointment_team_member_email' => $team_member->user_email ?? '',
        ];

        $appointment_cancel_email_data = apply_filters( 'booktics_appointment_cancel_email_data', $appointment_cancel_email_data );

        do_action( 'global_notification_hook', 'appointment_cancelled', $appointment_cancel_email_data );
    }

    /**
     * Check if a given request has access to cancel a specific item.
     *
     * @param WP_REST_Request $request Full data about the request.
     *
     * @return WP_Error|boolean
     */
    public function cancel_item_permissions_check( $request ) {
        return true;
    }

    /**
     * Prepare the item for create or update operation.
     *
     * @param WP_REST_Request $request Request object.
     *
     * @return bool|\WP_Error $prepared_item
     */
    public function prepare_item_for_database( $request ) {
        $data     = json_decode( $request->get_body(), true );
        $validate = booktics_validate(
            $data,
            array(
                'start_time'     => array(
                    'required',
                ),
                'end_time'       => array(
                    'required',
                ),
                'date'           => array( 'required' ),
                'service_id'     => array(
                    'required',
                ),
                'team_member_id' => array(
                    'required',
                ),
            )
        );

        if ( is_wp_error( $validate ) ) {
            return $validate;
        }

        $data = $this->get_sanitized_data( $data );

        return $data;
    }

    private function get_sanitized_data( $data ) {
        $data['start_time']      = ! empty( $data['start_time'] ) ? sanitize_text_field( $data['start_time'] ) : '';
        $data['end_time']        = ! empty( $data['end_time'] ) ? sanitize_text_field( $data['end_time'] ) : '';
        $data['date']            = ! empty( $data['date'] ) ? sanitize_text_field( $data['date'] ) : '';
        $data['service_id']      = ! empty( $data['service_id'] ) ? sanitize_text_field( $data['service_id'] ) : '';
        $data['team_member_id']  = ! empty( $data['team_member_id'] ) ? sanitize_text_field( $data['team_member_id'] ) : '';
        $data['customer_id']     = ! empty( $data['customer_id'] ) ? sanitize_text_field( $data['customer_id'] ) : '';
        $data['total_attendees'] = ! empty( $data['total_attendees'] ) ? sanitize_text_field( $data['total_attendees'] ) : '';
        $data['status']          = isset( $data['status'] ) ? $data['status'] : 'pending';

        return $data;
    }

    /**
     * Check if a given request has access to get time slots.
     *
     * @param $request
     *
     * @return boolean
     */
    public function get_time_slots_permissions_check( $request ) {
        return true;
    }

    /**
     * Fetch appointment time slots
     *
     * @param WP_REST_Request $request Request object.
     *
     * @return array|\WP_HTTP_Response
     * @throws Exception If there's an error getting time slots.
     */
    public function get_time_slots( $request ) {
        $service_id             = $request['service_id'];
        $additional_duration_id = $request['additional_duration_id'];
        $team_member_id         = $request['team_member_id'];
        $start_date             = $request['start_date'];
        $end_date               = $request['end_date'];
        $total_attendees        = $request['total_attendees'] ?? 1;

        $db = new Booktics_Database();

        $service_schedules = $db->table( booktics_get_table_name( 'booktics_schedules' ) )
                                ->where( 'service_id', '=', $service_id )
                                ->where_group(
                                    function ( $q ) use ( $start_date, $end_date ) {
                                        $q->where( 'custom_date', '=', '0' )
                                            ->or_where_group(
												function ( $q2 ) use ( $start_date, $end_date ) {
													$q2->where( 'custom_date', '>', $start_date )
                                                    ->where( 'custom_date', '<', $end_date );
												}
                                            );
                                    }
                                )
                                ->to_array();
        $service_schedules = new Service_Schedule( $service_schedules, $service_id );

        $team_member_schedules = $db->table( booktics_get_table_name( 'booktics_schedules' ) )
                                    ->where( 'team_member_id', '=', $team_member_id )
                                    ->where_group(
                                        function ( $q ) use ( $start_date, $end_date ) {
                                            $q->where( 'custom_date', '=', '0' )
                                                ->or_where_group(
													function ( $q2 ) use ( $start_date, $end_date ) {
														$q2->where( 'custom_date', '>', $start_date )
                                                        ->where( 'custom_date', '<', $end_date );
													}
                                                );
                                        }
                                    )
                                    ->to_array();
        $team_member_schedules = new Team_Member_Schedule( $team_member_schedules, $team_member_id );

        $default_schedules = $db->table( booktics_get_table_name( 'booktics_schedules' ) )
                                ->where( 'service_id', '=', 0 )
                                ->where( 'team_member_id', '=', 0 )
                                ->where_group(
                                    function ( $q ) use ( $start_date, $end_date ) {
                                        $q->where( 'custom_date', '=', '0' )
                                            ->or_where_group(
												function ( $q2 ) use ( $start_date, $end_date ) {
													$q2->where( 'custom_date', '>', $start_date )
                                                    ->where( 'custom_date', '<', $end_date );
												}
                                            );
                                    }
                                )
                                ->to_array();
        $default_schedules = new Default_Schedule( $default_schedules );

        $service                     = Service_Model::find( $service_id );
        $service_duration            = $this->get_service_duration_from_additional_durations( $service->additional_durations, $additional_duration_id );
        $service_duration            = apply_filters( 'booktics_service_duration_for_appointment_time_slots', $service_duration, $request );
        $slot_length                 = $service->interval_time < 1 ? booktics_get_option( 'interval_time', 30 ) : $service->interval_time;

        // Get buffer times from service
        $before_buffer_time = $service->before_buffer_time ? intval( $service->before_buffer_time ) : 0;
        $after_buffer_time  = $service->after_buffer_time ? intval( $service->after_buffer_time ) : 0;
        $options = array(
            'default_schedule'    => $default_schedules,
            'service_duration'    => $service_duration,
            'slot_length'         => $slot_length,
            'before_buffer_time'  => $before_buffer_time,
            'after_buffer_time'   => $after_buffer_time,
            'service_id'          => $service_id,
            'total_attendees'     => $total_attendees,
        );
        // Generate time slots for the given date range by interval time
        $slots = ( new Time_Slot_Manager( $service_schedules, $team_member_schedules, $options ) )
            ->generate_slots_for_range( $start_date, $end_date );


        $response                    = array(
            'service_id'             => $service_id,
            'team_member_id'         => $team_member_id,
            'additional_duration_id' => $additional_duration_id,
            'start_date'             => $start_date,
            'end_date'               => $end_date,
            'slots'                  => $slots,
        );

        return $this->response( $response, __( 'Successfully fetched time slots', 'booktics' ) );
    }

    /**
     * Get service duration from additional durations using additional_duration_id
     * If additional_duration_id is not found, return default duration (30 minutes)
     *
     * @param array $additional_durations Array of additional durations.
     * @param int $additional_duration_id ID of additional duration.
     *
     * @return int Service duration in minutes.
     */
    private function get_service_duration_from_additional_durations( $additional_durations, $additional_duration_id ) {
        $default_duration = 30;
        if ( empty( $additional_durations ) ) {
            return $default_duration; // Return default duration if additional durations are empty
        }

        foreach ( $additional_durations as $additional_duration ) {
            if ( $additional_duration['id'] === $additional_duration_id ) {
                return $this->get_converted_duration( $additional_duration['duration_time'], $additional_duration['duration_unit'] );
            }
        }

        return $default_duration;
    }

    /**
     * Convert duration to minutes
     *
     * @param int $duration Duration in minutes or hours.
     * @param string $unit Unit of duration (min or hour).
     *
     * @return int Duration in minutes.
     */
    private function get_converted_duration($duration, $unit) {
        if ( $unit === 'min' ) {
            return $duration;
        } elseif ( $unit === 'hour' ) {
            return $duration * 60;
        }
    }
}

```
