guard_staff(); if ( $check ) { return $check; } if ( null !== $date_from && ! $this->is_valid_date( $date_from ) ) { return [ 'error' => 'date_from must be in Y-m-d format.' ]; } try { $params = [ 'per_page' => min( 50, max( 1, (int) ( $per_page ?? 10 ) ) ), 'paged' => max( 1, (int) ( $paged ?? 1 ) ), ]; if ( null !== $date_from ) { $params['date_from'] = $date_from; } if ( ! empty( $status ) ) { $params['status'] = $status; } if ( null !== $service_id ) { $params['service_id'] = $service_id; } if ( null !== $customer_id ) { $params['customer_id'] = $customer_id; } if ( ! empty( $search ) ) { $params['search'] = $search; } // Team members are locked to their own bookings; admins may filter freely. $scoped_team_member = $this->enforced_team_member_id(); if ( null !== $scoped_team_member ) { $params['team_member_id'] = $scoped_team_member; } elseif ( null !== $team_member_id ) { $params['team_member_id'] = $team_member_id; } $request = new \WP_REST_Request( 'GET', '/booktics/v1/appointments' ); $request->set_query_params( $params ); $controller = new \Booktics\Appointment\Controllers\Appointment_Controller(); $result = $this->extract_response( $controller->get_items( $request ), 'Failed to retrieve appointments.' ); if ( isset( $result['error'] ) ) { return $result; } // Defence-in-depth: Booktics OR-combines the team_member_id filter with // `search` matches, so re-scope staff to their own rows before shaping. if ( $this->is_staff_only() ) { $result['items'] = $this->filter_to_own_appointments( $result['items'] ?? [] ); $result['total'] = count( $result['items'] ); } return $this->shape_paginated( $result, fn( $appointment ) => $this->shape_appointment( $appointment ) ); } catch ( \Throwable $e ) { Log::error( 'Booktics_List_Appointments_Tool error', [ 'error' => $e->getMessage() ] ); return [ 'error' => 'Failed to retrieve appointments: ' . $e->getMessage() ]; } } }