# booktics/1.0.13/core/service/controllers/service-controller.php

Booktics – Appointment Booking Calendar for Service Businesses, version 1.0.13. 703 lines.

- Page: https://pluginprobe.com/plugins/booktics/1.0.13/code/core/service/controllers/service-controller.php
- Raw: https://pluginprobe.com/plugins/booktics/1.0.13/raw/core/service/controllers/service-controller.php
- Modified: 2026-01-07T04:33:10+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.13/code/core/service/controllers/service-controller.php#L10-L20`.

```php
<?php

namespace Booktics\Service\Controllers;

use Booktics\Abstracts\Base_Rest_Controller;
use Booktics\Models\Service_Model;
use Booktics\Models\Team_Member_Model;
use Booktics\Services\Schedule_Manager;
use Booktics\Services\Schedule_Record;
use Booktics\Taxonomy\Category;
use Booktics\Service\Helpers\Service_Helper;
use Exception;
use WP_Error;
use WP_HTTP_Response;
use WP_REST_Server;

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

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

    /**
     * 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]+)/clone', array(
                array(
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => array( $this, 'clone_item' ),
                    'permission_callback' => array(
                        $this,
                        'create_item_permissions_check',
                    ),
                ),
            )
        );
    }

    /**
     * Get one item from the collection.
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     */
    public function get_item( $request ) {
        $id = intval( $request['id'] );

        $service = Service_Model::find( $id );

        if ( ! $service ) {
            return $this->error( __( 'Service not found', 'booktics' ), 404 );
        }
        $service = $service->to_array();

        $team_member_ids                = is_array( $service['team_member'] )
            ? array_unique( $service['team_member'] ) : array( $service['team_member'] );
        $service['team_member_details'] = Service_Helper::team_member_details( $team_member_ids );
        $service['team_member']         = $team_member_ids;

        $category_ids                = $this->get_service_category_ids( $service['id'] );
        $service['category_details'] = Service_Helper::category_details( $category_ids );
        $service['categories']       = $category_ids;

        $schedule_record     = ( new Schedule_Record() )
            ->get_schedules( $id, 'service' );
        $service['schedule'] = empty( $schedule_record['schedule'] ) ? (object) array()
            : $schedule_record['schedule'];

        $service['custom']    = $schedule_record['custom'];
        $service['holiday']   = $schedule_record['holiday'];
        $service['shortcode'] = '[booktics_service_view id="' . $service['id'] . '"]';
        $service              = apply_filters( 'booktics_service_format', $service );

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

    /**
     * Check if a given request has access to get a specific item.
     *
     * @param $request
     *
     * @return WP_Error
     */
    public function get_item_permissions_check( $request ): bool {
        return true;
    }

    /**
     * Get a collection of items.
     *
     * @param $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;

        $args = array(
            'per_page'   => $per_page,
            'paged'      => $paged,
            'meta_query' => array(),
        );

        if ( ! empty( $request['team_member_id'] ) ) {
            $args['meta_query'][] = array(
                'key'     => 'team_member',
                'value'   => sprintf( 'i:%d;', intval( $request['team_member_id'] ) ),
                'compare' => 'LIKE',
            );
        }

        if ( ! empty( $request['search'] ) ) {
            $args['s'] = sanitize_text_field( $request['search'] );
        }

        if ( ! empty( $args['meta_query'] ) ) {
            $args['meta_query']['relation'] = 'AND';
        }

        $args = apply_filters( 'booktics_service_args', $args, $request );

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

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

        if ( ! empty( $request['category_id'] ) ) {
            $service_ids_for_category = get_term_meta( $request['category_id'], 'booktics_category_services', true); 
        }

        if ( ! empty( $data['items'] ) ) {
            $enhanced_items = array();
            foreach ( $data['items'] as $service ) {
                $service_array = $service->to_array();
                
                if ( isset( $service_ids_for_category ) && ! in_array( $service_array['id'], $service_ids_for_category ) ) {
                    continue;
                }

                if ( ! empty( $service_array['team_member'] ) ) {
                    $team_member_ids                      = is_array( $service->team_member ) ? array_unique( $service->team_member ) : array( $service->team_member );
                    $service->team_member                 = $team_member_ids;
                    $service_array['team_member_details'] = Service_Helper::team_member_details( $team_member_ids );
                }
                $category_ids = $this->get_service_category_ids( $service_array['id'] );
                if ( ! empty( $category_ids ) ) {
                    $service->categories               = $category_ids;
                    $service_array['category_details'] = Service_Helper::category_details( $category_ids );
                }
                $service_array['shortcode'] = '[booktics_service_view id="' . $service_array['id'] . '"]';
                $service_array              = apply_filters( 'booktics_service_format', $service_array );
                $enhanced_items[]           = $service_array;
            }
            $data['items'] = $enhanced_items;
        }

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

    /**
     * Get all category IDs linked to a given service
     *
     * @param int $service_id
     * @return array
     */
    private function get_service_category_ids( int $service_id ): array {
        $args = array(
            'taxonomy'   => 'booktics_service_category', // Category taxonomy
            'hide_empty' => false,
            'meta_query' => array(
                array(
                    'key'     => 'booktics_category_services',
                    'value'   => sprintf( 'i:%d;', $service_id ),
                    'compare' => 'LIKE',
                ),
            ),
            'fields' => 'ids', // returning only ids
        );

        $categories = get_terms( $args );

        if ( is_wp_error( $categories ) || empty( $categories ) ) {
            return [];
        }

        return $categories;
    }


    /**
     * Check if a given request has access to get items.
     *
     * @param $request
     *
     * @return WP_Error
     */
    public function get_items_permissions_check( $request ): bool {
        return true;
    }

    /**
     * Create one item from the collection.
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     * @throws Exception
     */
    public function create_item( $request ) {
        $data = $this->prepare_item_for_database( $request );
        if ( is_wp_error( $data ) ) {
            return $this->error( $data->get_error_message() );
        }

        $service = Service_Model::create( $data );

        do_action( 'booktics_service_created', $service, $request );

        if ( ! empty( $data['team_member'] ) && is_array( $data['team_member'] ) ) {
            $this->update_team_members( $service->id, $data['team_member'] );
            $this->remove_service_from_other_team_members( $service->id, $data['team_member'] );
        }

        if ( is_wp_error( $service ) ) {
            return $this->error( $service->get_error_message() );
        }
        $service                        = $service->to_array();
        $service['shortcode']           = '[booktics_service_view id="' . $service['id'] . '"]';
        $service['category_details']    = Service_Helper::category_details( $service['categories'] );
        $service['team_member_details'] = Service_Helper::team_member_details( $service['team_member'] );
        $service                        = apply_filters( 'booktics_service_format', $service );

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

    /**
     * Check if a given request has access to create items.
     *
     * @param $request
     *
     * @return WP_Error
     */
    public function create_item_permissions_check( $request ): bool {
        return current_user_can( 'manage_options' );
    }

    /**
     * Update one item from the collection.
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     * @throws Exception
     */
    public function update_item( $request ) {
        $id   = (int) $request->get_param( 'id' );
        $data = $this->prepare_item_for_database( $request );

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

        $service = Service_Model::find( $id );

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

        $result = $service->update( $data );

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

        if ( ! empty( $data['team_member'] ) && is_array( $data['team_member'] ) ) {
            $this->update_team_members( $id, $data['team_member'] );
            $this->remove_service_from_other_team_members( $id, $data['team_member'] );
        }

        $input_data = json_decode( $request->get_body(), true );
        $this->update_service_schedule( $id, $input_data );

        do_action( 'booktics_service_updated', $service, $request );

        $response = $this->format_service_response( $id );
        return $this->response( $response, __( 'Successfully updated service', 'booktics' ) );
    }

    /**
     * Update team members for a service
     */
    private function update_team_members( $service_id, $team_members ) {
        foreach ( $team_members as $team_member_id ) {
            $team_member_model    = ( new Team_Member_Model( $team_member_id ) );
            $team_member          = $team_member_model->find( $team_member_id );
            $team_member_services = $team_member->services == '' ? array() : $team_member->services;

            if ( ! in_array( (int) $service_id, $team_member_services ) ) {
                $team_member_services[] = (int) $service_id;
                if ( $team_member ) {
                    $team_member->update(
                        array(
                            'services' => $team_member_services,
                        )
                    );
                }
            }
        }
    }

    /**
     * Remove service from other team members not in the list
     */
    private function remove_service_from_other_team_members( $service_id, $team_members ) {
        $other_team_members = ( new Team_Member_Model() )
            ->where( 'services', $service_id, 'LIKE' )
            ->all( array() );

        if ( ! empty( $other_team_members['items'] ) ) {
            foreach ( $other_team_members['items'] as $other_team_member ) {
                if ( ! in_array( $other_team_member['id'], $team_members ) ) {
                    $other_services = $other_team_member['services'];
                    if ( ( $key = array_search( $service_id, $other_services ) ) !== false ) {
                        unset( $other_services[ $key ] );
                        ( new Team_Member_Model( $other_team_member['id'] ) )->update(
                            array(
                                'services' => array_values( $other_services ),
                            )
                        );
                    }
                }
            }
        }
    }

    /**
     * Update the service schedule if provided
     */
    private function update_service_schedule( $service_id, $input_data ) {
        if ( isset( $input_data['schedule'] ) || isset( $input_data['custom'] ) || isset( $input_data['holiday'] ) ) {
            $service_schedule = Schedule_Manager::make( $input_data );
            $service_schedule->with_service_id( $service_id );
            $service_schedule->save();
        }
    }

    /**
     * Format the service response after update
     */
    private function format_service_response( $service_id ) {
        $response                        = Service_Model::find( $service_id )->to_array();
        $response['shortcode']           = '[booktics_service_view id="' . $response['id'] . '"]';
        $response['team_member_details'] = Service_Helper::team_member_details( $response['team_member'] );
        if ( ! empty( $response['categories'] ) ) {
            $category_ids                 = is_array( $response['categories'] ) ? $response['categories'] : array( $response['categories'] );
            $response['category_details'] = Service_Helper::category_details( $category_ids );
        }
        return apply_filters( 'booktics_service_format', $response );
    }

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

    /**
     * Delete one item from the collection.
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     */
    public function delete_item( $request ) {
        $id = intval( $request['id'] );

        $service = Service_Model::find( $id );

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

        $deleted = $service->delete();

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

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

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

    /**
     * Delete a collection of items
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     */
    public function delete_items( $request ) {
        $ids = $request['ids'];

        if ( ! is_array( $ids ) ) {
            return $this->error( __( 'Ids should be an array of items ids', 'booktics' ) );
        }

        $count = 0;
        $total = count( $ids );

        foreach ( $ids as $id ) {
            $service = Service_Model::find( $id );

            if ( $service ) {
                $service->delete();
                ++$count;
            }
        }

        if ( $count === 0 ) {
            return $this->error( __( 'No item deleted.', 'booktics' ) );
        }

        $message = sprintf( 'Deleted %d items of %d', $count, $total );

        return $this->response( array(), $message );
    }

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

    /**
     * Prepare the item for create or update operation.
     *
     * @param $request
     *
     * @return WP_Error
     */
    public function prepare_item_for_database( $request ) {
        $data     = json_decode( $request->get_body(), true );
        $validate = booktics_validate(
            $data, array(
                'title' => array(
                    'required',
                ),
            )
        );

        if ( is_wp_error( $validate ) ) {
            return $validate;
        }
        if ( empty( $data['team_member'] ) ) {
            $admin_query = new \WP_User_Query(
                array(
					'role' => array( 'administrator', 'booktics_team_member' ),
                )
            );
            $admins = $admin_query->get_results();
            $admin = reset( $admins );
            $team_member_id = $admin->id;
            $data['team_member'] = array( $team_member_id );
        }

        $data['title']              = ! empty( $data['title'] ) ? sanitize_text_field( $data['title'] ) : '';
        $data['description']        = ! empty( $data['description'] ) ? sanitize_text_field( $data['description'] ) : '';
        $data['content']            = ! empty( $data['content'] ) ? sanitize_text_field( $data['content'] ) : '';
        $data['link']               = ! empty( $data['link'] ) ? sanitize_text_field( $data['link'] ) : '';
        $data['image']              = ! empty( $data['image'] ) ? esc_url_raw( $data['image'] ) : '';
        $data['image_id']           = ! empty( $data['image_id'] ) ? esc_url_raw( $data['image_id'] ) : '';
        $data['interval_time']      = ! empty( $data['interval_time'] ) ? sanitize_text_field( $data['interval_time'] ) : 0;
        $data['before_buffer_time'] = ! empty( $data['before_buffer_time'] ) ? sanitize_text_field( $data['before_buffer_time'] ) : 0;
        $data['after_buffer_time']  = ! empty( $data['after_buffer_time'] ) ? sanitize_text_field( $data['after_buffer_time'] ) : 0;
        $data['show_end_time']      = ! empty( $data['show_end_time'] ) ? sanitize_text_field( $data['show_end_time'] ) : false;
        $data['status']             = ! empty( $data['status'] ) ? sanitize_text_field( $data['status'] ) : 'deactive';
        $data['team_member']        = empty( $data['team_member'] ) ? array() : array_unique( $data['team_member'] );
        $data['categories']         = empty( $data['categories'] ) ? array() : array_unique( $data['categories'] );
        $sanitized_durations        = array();
        if ( ! empty( $data['additional_durations'] ) ) {
            foreach ( $data['additional_durations'] as $duration ) {
                if ( is_array( $duration ) ) {
                    $sanitized_durations[] = array(
                        'id'             => ! empty( $duration['id'] ) ? sanitize_text_field( $duration['id'] ) : '',
                        'name'           => ! empty( $duration['name'] ) ? sanitize_text_field( $duration['name'] ) : '',
                        'duration_time'  => ! empty( $duration['duration_time'] ) ? sanitize_text_field( $duration['duration_time'] ) : 0,
                        'duration_unit'  => ! empty( $duration['duration_unit'] ) ? sanitize_text_field( $duration['duration_unit'] ) : '',
                        'charge_amount'  => ! empty( $duration['charge_amount'] ) ? (float) number_format( (float) $duration['charge_amount'], 4, '.', '' ) : '0.0000',
                        'deposit_amount' => ! empty( $duration['deposit_amount'] ) ? (float) number_format( (float) $duration['deposit_amount'], 4, '.', '' ) : '0.0000',
                    );
                }
            }
        } else {
            $sanitized_durations = array(
                array(
                    'id'             => 'default',
                    'name'           => 'default',
                    'duration_time'  => 30,
                    'duration_unit'  => 'min',
                    'charge_amount'  => 0,
                    'deposit_amount' => 0,
                ),
            );
        }
        $data['additional_durations'] = $sanitized_durations;
        $data                         = apply_filters( 'booktics_prepare_service_custom_fields', $data );
        $image_url                    = $request->get_param( 'image' );
        if ( $image_url ) {
            $data['image'] = esc_url_raw( $image_url );
        }

        return $data;
    }

    /**
     * Clone service from the record.
     *
     * @param $request
     *
     * @return WP_HTTP_Response
     * @throws Exception
     */
    public function clone_item( $request ) {
        $id = intval( $request['id'] );

        $service = Service_Model::find( $id );

        if ( ! $service ) {
            return $this->error( __( 'Service not found', 'booktics' ), 404 );
        }
        $clone_data = [
            'title'                              => $service->title . __( ' Copy', 'booktics' ),
            'description'                        => $service->description,
            'content'                            => $service->content,
            'image'                              => $service->image,
            'image_id'                           => $service->image_id,
            'categories'                         => $service->categories,
            'interval_time'                      => $service->interval_time,
            'before_buffer_time'                 => $service->before_buffer_time,
            'after_buffer_time'                  => $service->after_buffer_time,
            'team_member'                        => $service->team_member,
            'show_end_time'                      => $service->show_end_time,
            'additional_durations'               => $service->additional_durations,
            'enable_custom_holiday'              => $service->enable_custom_holiday,
            'enable_custom_schedule'             => $service->enable_custom_schedule,
            'enable_additional_service_duration' => $service->enable_additional_service_duration,
        ];

        $clone_data = apply_filters( 'booktics_clone_service_data', $clone_data, $service );

        $cloned = Service_Model::create( $clone_data );

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

        $schedule_record           = ( new Schedule_Record() )
            ->get_schedules( $id, 'service' );
        $schedule_data['schedule'] = $schedule_record['schedule'];
        $schedule_data['custom']   = $schedule_record['custom'];
        $schedule_data['holiday']  = $schedule_record['holiday'];

        $service_schedule = Schedule_Manager::make( $schedule_data );
        $service_schedule->with_service_id( $cloned->id );
        $service_schedule->save();

        $team_member_details           = Service_Helper::team_member_details( $service->team_member );
        $cloned                        = $cloned->to_array();
        $cloned['team_member_details'] = $team_member_details;
        $cloned['category_details']    = Service_Helper::category_details( $cloned['categories'] );

        $clone_schedule_record = ( new Schedule_Record() )
            ->get_schedules( $cloned['id'], 'service' );
        $cloned['schedule']    = empty( $clone_schedule_record['schedule'] ) ? (object) array()
            : $clone_schedule_record['schedule'];
        $cloned['custom']      = $clone_schedule_record['custom'];
        $cloned['holiday']     = $clone_schedule_record['holiday'];
        $cloned['shortcode']   = '[booktics_service_view id="' . $cloned['id'] . '"]';
        $cloned                = apply_filters( 'booktics_service_format', $cloned );

        return $this->response( $cloned, __( 'Successfully cloned service', 'booktics' ) );
    }
}

```
