cart_manager = new Cart_Manager(); } /** * Register REST API routes for cart functionality * * @return void */ public function register_routes(): void { register_rest_route( $this->namespace, $this->base, array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'add_to_cart' ), 'permission_callback' => '__return_true', ) ); register_rest_route( $this->namespace, $this->base . '/(?P[a-zA-Z0-9-]+)', array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_from_cart' ), 'permission_callback' => '__return_true', ) ); register_rest_route( $this->namespace, $this->base, array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'sync_cart' ), 'permission_callback' => array( $this, 'is_user_logged_in' ), ) ); register_rest_route( $this->namespace, $this->base . '/(?P[a-zA-Z0-9-]+)', array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'edit_cart_item' ), 'permission_callback' => '__return_true', ) ); register_rest_route( $this->namespace, $this->base, array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_cart_items' ), 'permission_callback' => '__return_true', ) ); } /** * Prepare and validate cart item data * * @param array $data * * @return []|array|bool|WP_Error */ private function prepare_item_to_save( array $data ) { $validated = booktics_validate( $data, array( 'date' => array( 'required' ), 'start_time' => array( 'required' ), 'service_id' => array( 'required' ), 'team_member_id' => array( 'required' ), ) ); if ( is_wp_error( $validated ) ) { return $validated; } $item_validation = $this->validate_cart_item( $data ); if ( is_wp_error( $item_validation ) ) { return $item_validation; } return $item_validation; } /** * Add an item to the cart * * @param $request * * @return array|\WP_HTTP_Response */ public function add_to_cart( $request ) { $params = json_decode( $request->get_body(), true ); $validated = $this->prepare_item_to_save( $params ); if ( is_wp_error( $validated ) ) { return $this->error( __( 'Cart validation error', 'booktics' ), 422, 'Validation', $validated->errors ); } // Calculate end time from start time and total duration $total_duration_min = $this->calculate_total_duration( $validated ); $validated['end_time'] = $this->get_endtime_from_starttime($validated['start_time'], $total_duration_min); $response = $this->cart_manager->add_to_cart( $validated ); $response['service'] = $this->get_service( $response['service_id'] ); $response['team'] = $this->get_team_member( $response['team_member_id'] ); // Allow booktics-pro to add location details $response = apply_filters( 'booktics_cart_item_response', $response ); return $this->response( $response, __( 'Item added to cart', 'booktics' ), 201 ); } /** * Delete an item from the user's cart * * @param $request * * @return array|\WP_HTTP_Response */ public function delete_from_cart( $request ) { $uuid = sanitize_text_field( $request->get_param( 'uuid' ) ); if ( empty( $uuid ) ) { return $this->error( __( 'UUID is required', 'booktics' ), 422, 'Validation' ); } $this->cart_manager->remove_cart_item( $uuid ); return $this->response( array(), __( 'Successfully deleted item from cart.', 'booktics' ) ); } /** * Synchronize cart session data to database for logged-in user * * @param $request * * @return array|\WP_HTTP_Response */ public function sync_cart( $request ) { $user_id = get_current_user_id(); $this->cart_manager->sync_session_to_db( $user_id ); $response = $this->cart_manager->get_cart(); return $this->response( $response, __( 'Successfully synced cart.', 'booktics' ) ); } /** * Get all items from the cart * * @return array|\WP_HTTP_Response */ public function get_cart_items() { $response = $this->cart_manager->get_cart(); $response['items'] = isset( $response['items'] ) ? array_map( function ( $item ) { if ( ! is_array( $item ) ) { $item = (array) $item; } $item['service'] = $this->get_service( $item['service_id'] ); $item['team'] = $this->get_team_member( $item['team_member_id'] ); // Allow booktics-pro to add location details $item = apply_filters( 'booktics_cart_item_response', $item ); return $item; }, $response['items'] ) : array(); return $this->response( $response, __( 'Cart items fetched successfully', 'booktics' ) ); } /** * Check if the current user is logged in * * @return bool True if user is logged in, false otherwise */ public function is_user_logged_in() { return is_user_logged_in(); } /** * Validate service, team member and price * * @param array $data Cart item data * * @return array|WP_Error Returns true if valid, WP_Error otherwise */ private function validate_cart_item( array $data ) { $error = new WP_Error(); $service = ( new Service_Model() )->find( $data['service_id'] ); if ( ! $service ) { $error->add( 'invalid_service', __( 'Invalid service selected', 'booktics' ) ); } $team_member = ( new Team_Member_Model() )->find( $data['team_member_id'] ); if ( empty( $team_member ) ) { $error->add( 'invalid_team_member', __( 'Invalid team member selected', 'booktics' ) ); } $service_price = 0; $addition_durations = $service->additional_durations; if ( ! empty( $addition_durations ) && isset( $data['additional_duration_id'] ) ) { $addition_durations = array_filter( $addition_durations, function ( $duration ) use ( $data ) { return $duration['id'] === $data['additional_duration_id']; } ); $addition_duration = reset( $addition_durations ); $service_price = $addition_duration['charge_amount'] ?? 0; } $data['price'] = $service_price; return $error->has_errors() ? $error : $data; } /** * Edit a cart item * * @param $request * * @return array|\WP_HTTP_Response */ public function edit_cart_item( $request ) { $uuid = sanitize_text_field( (string) $request->get_param( 'uuid' ) ); $params = json_decode( $request->get_body(), true ); if ( empty( $uuid ) ) { return $this->error( __( 'UUID is required', 'booktics' ), 422, 'Validation' ); } $validated = $this->prepare_item_to_save( $params ); if ( is_wp_error( $validated ) ) { return $this->error( __( 'Cart validation error', 'booktics' ), 422, 'Validation', $validated->errors ); } // Add UUID to validated data $validated['uuid'] = $uuid; // Calculate end time from start time and total duration $total_duration_min = $this->calculate_total_duration( $validated ); $validated['end_time'] = $this->get_endtime_from_starttime( $validated['start_time'], $total_duration_min ); // Update the cart item $updated_item = $this->cart_manager->edit_cart_item( $uuid, $validated ); if ( empty( $updated_item ) ) { return $this->error( __( 'Cart item not found', 'booktics' ), 404, 'NotFound' ); } // Allow booktics-pro to add location details $updated_item = apply_filters( 'booktics_cart_item_response', $updated_item ); return $this->response( $updated_item, __( 'Successfully updated cart item', 'booktics' ) ); } /** * @param $service_id * * @return array */ public function get_service( $service_id ): array { $service = Service_Model::find( $service_id ); if ( empty( $service ) ) { return array(); } return array( 'id' => $service->id, 'title' => $service->title, ); } /** * @param $team_member_id * * @return array */ public function get_team_member( $team_member_id ): array { $team_member = ( new Team_Member_Model() )->find( $team_member_id ); if ( empty( $team_member ) ) { return array(); } $team_member = $team_member->to_array(); return array( 'id' => $team_member['id'], 'display_name' => $team_member['display_name'] ?? '', 'image' => $team_member['image'] ?? '', ); } /** * Calculate total duration for a cart item (appointment) * * @param array $item Cart item data * * @return int Total duration in minutes */ private function calculate_total_duration( $item ) { $service_model = Service_Model::find( $item['service_id'] ); if ( ! $service_model ) { return 0; } $service_duration = array_filter( $service_model->additional_durations, function ( $duration ) use ( $item ) { return $item['additional_duration_id'] === $duration['id']; } ); if ( empty( $service_duration ) ) { return 0; } $service_duration = reset( $service_duration ); $total_duration_min = $service_duration['duration_unit'] === 'min' ? $service_duration['duration_time'] : ( $service_duration['duration_time'] * 60 ); if ( ! empty( $item['extra_services'] ) ) { $total_duration_min = apply_filters( 'booktics_extra_service_duration', $total_duration_min, $item['extra_services'] ); } $total_duration_min = apply_filters( 'booktics_calculate_total_duration', $total_duration_min, $item ); return $total_duration_min; } /** * Get end time for appointments from start time and total duration * * @param $start_time start time of the appointment * @param $total_duration_min total duration of the appointment in minutes including service time and extra service time * * @return string end time of the appointment */ private function get_endtime_from_starttime( $start_time, $total_duration_min ) { $start_time = strtotime( $start_time ); $end_time = strtotime( '+' . $total_duration_min . ' minutes', $start_time ); return gmdate( 'H:i', $end_time ); } }