* @link https://descomplicar.pt * @since 1.0.0 */ namespace KiviCare_API\Utils; use WP_Error; if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class Input_Validator * * Centralized input validation for all API operations * * @since 1.0.0 */ class Input_Validator { /** * Validate clinic data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_clinic_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation if ( empty( $data['name'] ) ) { $errors[] = 'Clinic name is required'; } } // Validate email format if provided if ( ! empty( $data['email'] ) && ! is_email( $data['email'] ) ) { $errors[] = 'Invalid email format'; } // Validate phone number format if provided if ( ! empty( $data['telephone_no'] ) && ! self::validate_phone_number( $data['telephone_no'] ) ) { $errors[] = 'Invalid phone number format'; } // Validate specialties if provided if ( ! empty( $data['specialties'] ) && ! self::validate_specialties( $data['specialties'] ) ) { $errors[] = 'Invalid specialties format or values'; } if ( ! empty( $errors ) ) { return new WP_Error( 'clinic_validation_failed', 'Clinic validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate patient data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_patient_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation $required_fields = array( 'first_name', 'last_name', 'clinic_id' ); foreach ( $required_fields as $field ) { if ( empty( $data[$field] ) ) { $errors[] = ucfirst( str_replace( '_', ' ', $field ) ) . ' is required'; } } } // Validate email format if provided if ( ! empty( $data['user_email'] ) && ! is_email( $data['user_email'] ) ) { $errors[] = 'Invalid email format'; } // Validate phone number format if provided if ( ! empty( $data['contact_no'] ) && ! self::validate_phone_number( $data['contact_no'] ) ) { $errors[] = 'Invalid contact number format'; } // Validate date of birth if provided if ( ! empty( $data['dob'] ) && ! self::validate_date( $data['dob'] ) ) { $errors[] = 'Invalid date of birth format'; } // Validate gender if provided if ( ! empty( $data['gender'] ) && ! self::validate_gender( $data['gender'] ) ) { $errors[] = 'Invalid gender value'; } if ( ! empty( $errors ) ) { return new WP_Error( 'patient_validation_failed', 'Patient validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate doctor data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_doctor_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation $required_fields = array( 'first_name', 'last_name', 'clinic_id' ); foreach ( $required_fields as $field ) { if ( empty( $data[$field] ) ) { $errors[] = ucfirst( str_replace( '_', ' ', $field ) ) . ' is required'; } } } // Validate email format if provided if ( ! empty( $data['user_email'] ) && ! is_email( $data['user_email'] ) ) { $errors[] = 'Invalid email format'; } // Validate mobile number format if provided if ( ! empty( $data['mobile_number'] ) && ! self::validate_phone_number( $data['mobile_number'] ) ) { $errors[] = 'Invalid mobile number format'; } // Validate specialties if provided if ( ! empty( $data['specialties'] ) && ! self::validate_specialties( $data['specialties'] ) ) { $errors[] = 'Invalid specialties format or values'; } // Validate license number format if provided if ( ! empty( $data['license_number'] ) && ! self::validate_license_number( $data['license_number'] ) ) { $errors[] = 'Invalid license number format'; } if ( ! empty( $errors ) ) { return new WP_Error( 'doctor_validation_failed', 'Doctor validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate appointment data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_appointment_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation $required_fields = array( 'patient_id', 'doctor_id', 'clinic_id', 'appointment_start_date', 'appointment_start_time' ); foreach ( $required_fields as $field ) { if ( empty( $data[$field] ) ) { $errors[] = ucfirst( str_replace( '_', ' ', $field ) ) . ' is required'; } } } // Validate appointment date if provided if ( ! empty( $data['appointment_start_date'] ) && ! self::validate_date( $data['appointment_start_date'] ) ) { $errors[] = 'Invalid appointment start date format'; } // Validate appointment time if provided if ( ! empty( $data['appointment_start_time'] ) && ! self::validate_time( $data['appointment_start_time'] ) ) { $errors[] = 'Invalid appointment start time format'; } // Validate end time if provided if ( ! empty( $data['appointment_end_time'] ) && ! self::validate_time( $data['appointment_end_time'] ) ) { $errors[] = 'Invalid appointment end time format'; } // Validate duration if provided if ( ! empty( $data['duration'] ) && ! self::validate_duration( $data['duration'] ) ) { $errors[] = 'Invalid duration value'; } // Validate status if provided if ( ! empty( $data['status'] ) && ! self::validate_appointment_status( $data['status'] ) ) { $errors[] = 'Invalid appointment status'; } if ( ! empty( $errors ) ) { return new WP_Error( 'appointment_validation_failed', 'Appointment validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate encounter data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_encounter_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation $required_fields = array( 'patient_id', 'doctor_id', 'clinic_id' ); foreach ( $required_fields as $field ) { if ( empty( $data[$field] ) ) { $errors[] = ucfirst( str_replace( '_', ' ', $field ) ) . ' is required'; } } } // Validate encounter date if provided if ( ! empty( $data['encounter_date'] ) && ! self::validate_datetime( $data['encounter_date'] ) ) { $errors[] = 'Invalid encounter date format'; } // Validate status if provided if ( ! empty( $data['status'] ) && ! self::validate_encounter_status( $data['status'] ) ) { $errors[] = 'Invalid encounter status'; } // Validate SOAP notes if provided if ( ! empty( $data['soap_notes'] ) && ! self::validate_soap_notes( $data['soap_notes'] ) ) { $errors[] = 'Invalid SOAP notes format'; } // Validate vital signs if provided if ( ! empty( $data['vital_signs'] ) && ! self::validate_vital_signs( $data['vital_signs'] ) ) { $errors[] = 'Invalid vital signs format'; } if ( ! empty( $errors ) ) { return new WP_Error( 'encounter_validation_failed', 'Encounter validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate prescription data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_prescription_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation $required_fields = array( 'patient_id', 'doctor_id', 'medication_name', 'dosage', 'frequency' ); foreach ( $required_fields as $field ) { if ( empty( $data[$field] ) ) { $errors[] = ucfirst( str_replace( '_', ' ', $field ) ) . ' is required'; } } } // Validate prescription date if provided if ( ! empty( $data['prescription_date'] ) && ! self::validate_date( $data['prescription_date'] ) ) { $errors[] = 'Invalid prescription date format'; } // Validate dosage format if provided if ( ! empty( $data['dosage'] ) && ! self::validate_dosage( $data['dosage'] ) ) { $errors[] = 'Invalid dosage format'; } // Validate frequency if provided if ( ! empty( $data['frequency'] ) && ! self::validate_frequency( $data['frequency'] ) ) { $errors[] = 'Invalid frequency format'; } // Validate duration if provided if ( ! empty( $data['duration_days'] ) && ! self::validate_positive_integer( $data['duration_days'] ) ) { $errors[] = 'Duration must be a positive integer'; } // Validate status if provided if ( ! empty( $data['status'] ) && ! self::validate_prescription_status( $data['status'] ) ) { $errors[] = 'Invalid prescription status'; } if ( ! empty( $errors ) ) { return new WP_Error( 'prescription_validation_failed', 'Prescription validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate bill data * * @param array $data Data to validate * @param string $operation Operation type (create, update) * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_bill_data( $data, $operation = 'create' ) { $errors = array(); if ( $operation === 'create' ) { // Required fields for creation $required_fields = array( 'patient_id', 'clinic_id' ); foreach ( $required_fields as $field ) { if ( empty( $data[$field] ) ) { $errors[] = ucfirst( str_replace( '_', ' ', $field ) ) . ' is required'; } } } // Validate bill date if provided if ( ! empty( $data['bill_date'] ) && ! self::validate_date( $data['bill_date'] ) ) { $errors[] = 'Invalid bill date format'; } // Validate due date if provided if ( ! empty( $data['due_date'] ) && ! self::validate_date( $data['due_date'] ) ) { $errors[] = 'Invalid due date format'; } // Validate amounts if provided $amount_fields = array( 'subtotal_amount', 'tax_amount', 'discount_amount', 'total_amount', 'amount_paid' ); foreach ( $amount_fields as $field ) { if ( isset( $data[$field] ) && ! self::validate_currency_amount( $data[$field] ) ) { $errors[] = "Invalid {$field} format"; } } // Validate status if provided if ( ! empty( $data['status'] ) && ! self::validate_bill_status( $data['status'] ) ) { $errors[] = 'Invalid bill status'; } // Validate items if provided if ( ! empty( $data['items'] ) && ! self::validate_bill_items( $data['items'] ) ) { $errors[] = 'Invalid bill items format'; } if ( ! empty( $errors ) ) { return new WP_Error( 'bill_validation_failed', 'Bill validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Validate clinic list parameters * * @param array $params Parameters to validate * @return bool|WP_Error True if valid, WP_Error otherwise * @since 1.0.0 */ public static function validate_clinic_list_params( $params ) { $errors = array(); // Validate page parameter if ( isset( $params['page'] ) && ( ! is_numeric( $params['page'] ) || $params['page'] < 1 ) ) { $errors[] = 'Page must be a positive integer'; } // Validate per_page parameter if ( isset( $params['per_page'] ) && ( ! is_numeric( $params['per_page'] ) || $params['per_page'] < 1 || $params['per_page'] > 100 ) ) { $errors[] = 'Per page must be between 1 and 100'; } // Validate status parameter if ( isset( $params['status'] ) && ! in_array( $params['status'], array( 0, 1, '0', '1' ) ) ) { $errors[] = 'Status must be 0 or 1'; } if ( ! empty( $errors ) ) { return new WP_Error( 'list_params_validation_failed', 'List parameters validation failed', array( 'status' => 400, 'errors' => $errors ) ); } return true; } /** * Sanitize clinic data * * @param array $data Data to sanitize * @return array Sanitized data * @since 1.0.0 */ public static function sanitize_clinic_data( $data ) { $sanitized = array(); $text_fields = array( 'name', 'address', 'city', 'state', 'country', 'postal_code', 'telephone_no' ); foreach ( $text_fields as $field ) { if ( isset( $data[$field] ) ) { $sanitized[$field] = sanitize_text_field( $data[$field] ); } } if ( isset( $data['email'] ) ) { $sanitized['email'] = sanitize_email( $data['email'] ); } if ( isset( $data['specialties'] ) && is_array( $data['specialties'] ) ) { $sanitized['specialties'] = array_map( 'sanitize_text_field', $data['specialties'] ); } if ( isset( $data['clinic_admin_id'] ) ) { $sanitized['clinic_admin_id'] = absint( $data['clinic_admin_id'] ); } if ( isset( $data['status'] ) ) { $sanitized['status'] = absint( $data['status'] ); } return $sanitized; } /** * Sanitize patient data * * @param array $data Data to sanitize * @return array Sanitized data * @since 1.0.0 */ public static function sanitize_patient_data( $data ) { $sanitized = array(); $text_fields = array( 'first_name', 'last_name', 'patient_id', 'contact_no', 'address', 'city', 'state', 'country', 'postal_code', 'gender', 'blood_group', 'emergency_contact_name', 'emergency_contact_no' ); foreach ( $text_fields as $field ) { if ( isset( $data[$field] ) ) { $sanitized[$field] = sanitize_text_field( $data[$field] ); } } if ( isset( $data['user_email'] ) ) { $sanitized['user_email'] = sanitize_email( $data['user_email'] ); } if ( isset( $data['dob'] ) ) { $sanitized['dob'] = sanitize_text_field( $data['dob'] ); } if ( isset( $data['clinic_id'] ) ) { $sanitized['clinic_id'] = absint( $data['clinic_id'] ); } if ( isset( $data['status'] ) ) { $sanitized['status'] = absint( $data['status'] ); } return $sanitized; } /** * Validation helper methods */ private static function validate_phone_number( $phone ) { return preg_match( '/^[+]?[0-9\s\-\(\)]{7,20}$/', $phone ); } private static function validate_specialties( $specialties ) { if ( ! is_array( $specialties ) ) { return false; } $valid_specialties = array( 'general_medicine', 'cardiology', 'dermatology', 'endocrinology', 'gastroenterology', 'gynecology', 'neurology', 'oncology', 'ophthalmology', 'orthopedics', 'otolaryngology', 'pediatrics', 'psychiatry', 'pulmonology', 'radiology', 'urology', 'surgery', 'anesthesiology', 'pathology', 'emergency_medicine', 'family_medicine' ); foreach ( $specialties as $specialty ) { if ( ! in_array( $specialty, $valid_specialties ) ) { return false; } } return true; } private static function validate_date( $date ) { $d = \DateTime::createFromFormat( 'Y-m-d', $date ); return $d && $d->format( 'Y-m-d' ) === $date; } private static function validate_datetime( $datetime ) { $d = \DateTime::createFromFormat( 'Y-m-d H:i:s', $datetime ); return $d && $d->format( 'Y-m-d H:i:s' ) === $datetime; } private static function validate_time( $time ) { return preg_match( '/^([0-1]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/', $time ); } private static function validate_gender( $gender ) { return in_array( strtolower( $gender ), array( 'male', 'female', 'other' ) ); } private static function validate_duration( $duration ) { return is_numeric( $duration ) && $duration > 0 && $duration <= 480; // Max 8 hours } private static function validate_appointment_status( $status ) { return in_array( $status, array( 1, 2, 3, 4, 5 ) ); // Booked, Completed, Cancelled, No Show, Rescheduled } private static function validate_encounter_status( $status ) { return in_array( $status, array( 'draft', 'in_progress', 'finalized' ) ); } private static function validate_soap_notes( $soap_notes ) { if ( ! is_array( $soap_notes ) ) { return false; } $valid_sections = array( 'subjective', 'objective', 'assessment', 'plan' ); foreach ( array_keys( $soap_notes ) as $section ) { if ( ! in_array( $section, $valid_sections ) ) { return false; } } return true; } private static function validate_vital_signs( $vital_signs ) { if ( ! is_array( $vital_signs ) ) { return false; } $valid_vitals = array( 'temperature', 'blood_pressure_systolic', 'blood_pressure_diastolic', 'heart_rate', 'respiratory_rate', 'oxygen_saturation', 'weight', 'height', 'bmi' ); foreach ( array_keys( $vital_signs ) as $vital ) { if ( ! in_array( $vital, $valid_vitals ) ) { return false; } } return true; } private static function validate_dosage( $dosage ) { return preg_match( '/^\d+(\.\d+)?\s*(mg|g|ml|units?)$/i', $dosage ); } private static function validate_frequency( $frequency ) { $valid_frequencies = array( 'once daily', 'twice daily', 'three times daily', 'four times daily', 'every 4 hours', 'every 6 hours', 'every 8 hours', 'every 12 hours', 'as needed', 'before meals', 'after meals', 'at bedtime' ); return in_array( strtolower( $frequency ), $valid_frequencies ); } private static function validate_prescription_status( $status ) { return in_array( $status, array( 'active', 'completed', 'cancelled', 'discontinued' ) ); } private static function validate_bill_status( $status ) { return in_array( $status, array( 'draft', 'pending', 'paid', 'overdue', 'cancelled', 'refunded' ) ); } private static function validate_currency_amount( $amount ) { return is_numeric( $amount ) && $amount >= 0; } private static function validate_positive_integer( $value ) { return is_numeric( $value ) && $value > 0 && $value == (int) $value; } private static function validate_license_number( $license ) { return preg_match( '/^[A-Z0-9\-]{5,20}$/', $license ); } private static function validate_bill_items( $items ) { if ( ! is_array( $items ) ) { return false; } foreach ( $items as $item ) { if ( ! is_array( $item ) ) { return false; } $required_fields = array( 'name', 'quantity', 'unit_price' ); foreach ( $required_fields as $field ) { if ( ! isset( $item[$field] ) ) { return false; } } if ( ! is_numeric( $item['quantity'] ) || $item['quantity'] <= 0 ) { return false; } if ( ! is_numeric( $item['unit_price'] ) || $item['unit_price'] < 0 ) { return false; } } return true; } }