array( 'type' => 'floatval' ), 'user_id' => array( 'type' => 'intval' ), 'staff_id' => array( 'type' => 'intval' ), 'service_id' => array( 'type' => 'intval' ), 'start_timestamp' => array( 'type' => 'intval' ), 'end_timestamp' => array( 'type' => 'intval' ), 'now_timestamp' => array( 'type' => 'intval' ), 'today_timestamp' => array( 'type' => 'intval' ), 'email' => array( 'type' => 'strval', 'function' => array( 'custom' => false, 'name' => 'sanitize_email', ), ), 'payment_method' => array( 'type' => 'strval' ), 'full_name' => array( 'type' => 'strval' ), 'phone' => array( 'function' => array( 'custom' => true, 'name' => 'custom_sanitize_phone', ), ), ); } /** * Convert UTC offset to timezone name. * * @since 2.5.2 * * @param string $timezone_string The timezone string from wp_timezone_string(). * * @return string The timezone name or an empty string if no match is found. */ private static function convert_offset_to_timezone_name( $timezone_string ) { // Check if it's a UTC offset (format: +HH:MM or -HH:MM). if ( preg_match( '/^([+-])(\d{2}):(\d{2})$/', $timezone_string, $matches ) ) { $sign = ( $matches[1] === '-' ) ? -1 : 1; $hours = (int) $matches[2]; $minutes = (int) $matches[3]; $offset_seconds = $sign * ( $hours * 3600 + $minutes * 60 ); // Try to find a timezone name based on the offset. $timezone_name = timezone_name_from_abbr( '', $offset_seconds, 0 ); if ( $timezone_name !== false ) { return $timezone_name; } // If timezone_name_from_abbr() fails, search through timezone identifiers. $timezone_identifiers = DateTimeZone::listIdentifiers(); $now = new DateTime( 'now', new DateTimeZone( 'UTC' ) ); foreach ( $timezone_identifiers as $identifier ) { try { $tz = new DateTimeZone( $identifier ); $offset = $tz->getOffset( $now ); if ( $offset === $offset_seconds ) { return $identifier; } } catch ( Exception $e ) { continue; } } // If no match found, return an empty string. return ''; } // Already a timezone name, return as is. return $timezone_string; } /** * Validation * * @since 2.6.0 Booking identity is derived from the authenticated session in Registered mode. * * @param $data */ public static function validate( $data ) { $errors = array(); $settings = SettingsController::get_settings(); if ( ! $data['staff_id'] ) { $errors['staff_id'] = __( 'No staff', 'bookit' ); } if ( ! $data['service_id'] ) { $errors['service_id'] = __( 'No service', 'bookit' ); } if ( ! $data['start_time'] ) { $errors['start_time'] = __( 'No start time', 'bookit' ); } if ( ! $data['end_time'] ) { $errors['end_time'] = __( 'No end time', 'bookit' ); } if ( ! $data['date_timestamp'] ) { $errors['date_timestamp'] = __( 'No date', 'bookit' ); } $appointment = Appointments::checkAppointment( $data ); if ( $appointment > 0 ) { $errors['appointment'] = __( 'Selected Service Time is not available!', 'bookit' ); } if ( $data['phone'] || false === $data['phone'] ) { if ( ! preg_match( '/^((\+)?[0-9]{8,14})$/', $data['phone'] ) ) { $errors['phone'] = __( 'Please enter a valid phone number', 'bookit' ); } } if ( 'guest' == $settings['booking_type'] ) { if ( ! $data['email'] && ! $data['phone'] ) { $errors['phone'] = __( 'Please enter email or phone', 'bookit' ); } } if ( ! $data['email'] ) { $errors['email'] = __( 'Please enter email', 'bookit' ); } if ( $data['email'] && ! is_email( $data['email'] ) ) { $errors['email'] = __( 'Please enter your email in format youremail@example.com', 'bookit' ); } if ( $data['full_name'] ) { if ( strlen( $data['full_name'] ) < 3 || strlen( $data['full_name'] ) > 25 ) { $errors['full_name'] = __( 'Full name must be between 3 and 25 characters long', 'bookit' ); } } else { $errors['full_name'] = __( 'Please enter full name', 'bookit' ); } $price = Staff_Services::get_service_price_by_staff( $data['service_id'], $data['staff_id'] ); if ( (float) $data['clear_price'] !== (float) $price ) { $errors['clear_price'] = __( 'Price is incorrect', 'bookit' ); } if ( (float) $data['clear_price'] > 0 && ! array_key_exists( $data['payment_method'], $settings['payments'] ) ) { $errors['payment_method'] = __( 'Please choose correct payment method', 'bookit' ); } if ( count( $errors ) > 0 ) { wp_send_json_error( array( 'errors' => $errors ) ); } } /** * Book Appointment * * @since 2.6.0 Require authentication and link the booking to the current user in Registered mode. * @since 2.6.0.2 Derive the booking identity from the current session. */ public static function save() { $send_no_cache_headers = apply_filters( 'rest_send_nocache_headers', is_user_logged_in() ); $nonce = ''; if ( ! $send_no_cache_headers ) { $nonce = wp_create_nonce( 'bookit_nonce' ); $_SERVER['HTTP_X_WP_NONCE'] = $nonce; } check_ajax_referer( 'bookit_book_appointment', 'nonce' ); $data = CleanHelper::cleanData( $_POST, self::getCleanRules() ); // Identify the booking from the current session. $data['user_id'] = get_current_user_id(); $settings = SettingsController::get_settings(); if ( 'registered' == $settings['booking_type'] ) { // Registered booking is tied to the authenticated visitor. if ( ! is_user_logged_in() ) { wp_send_json_error( array( 'login_required' => true, 'message' => __( 'Please log in to complete your booking.', 'bookit' ), ) ); } $data['email'] = wp_get_current_user()->user_email; } self::validate( $data ); if ( empty( $data ) ) { wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) ); die(); } $customer = CustomerController::get_customer( $data ); $notes = array(); if ( ! empty( $data['comment'] ) ) { $notes['comment'] = $data['comment']; } if ( $customer->email != $data['email'] ) { $notes['email'] = $data['email']; } if ( $customer->phone != $data['phone'] && ! empty( $data['phone'] ) ) { $notes['phone'] = $data['phone']; } if ( $customer->full_name != $data['full_name'] && ! empty( $data['full_name'] ) ) { $notes['full_name'] = $data['full_name']; } $data['customer_id'] = $customer->id; $data['notes'] = serialize( $notes ); $data['status'] = Appointments::$pending; $id = Appointments::create_appointment( $data ); do_action( 'bookit_appointment_created', $id ); $appointment = (array) Appointments::get_full_appointment_by_id( $id ); $appointment['token'] = $data['token']; /** if google calendar addon is installed */ if ( Plugin::isAddonInstalledAndEnabled( self::$proAddon ) && has_action( 'bookit_google_calendar_create_appointment' ) ) { $appointment['customer_email'] = $notes['email'] ?? $appointment['customer_email']; $appointment['customer_phone'] = $notes['phone'] ?? $appointment['customer_phone']; do_action( 'bookit_google_calendar_create_appointment', $appointment ); } /** if google calendar addon is installed | end */ // Add timezone to the appointment. $appointment['timezone_name'] = self::convert_offset_to_timezone_name( wp_timezone_string() ); $redirect_url = ''; if ( ! is_null( $appointment['payment_method'] ) ) { $payments = new Payments( $appointment ); $redirect_url = $payments->redirect_url(); } wp_send_json_success( array( 'appointment' => $appointment, 'customer' => $customer, 'nonce' => $nonce, 'redirect_url' => $redirect_url, 'message' => __( 'Appointment Saved!', 'bookit' ), ) ); } /** * Get Month Appointments */ public static function get_month_appointments() { check_ajax_referer( 'bookit_month_appointments', 'nonce' ); $data = CleanHelper::cleanData( $_POST, self::getCleanRules() ); if ( ! empty( $data ) ) { $appointments = Appointments::month_appointments( $data ); wp_send_json_success( $appointments ); } wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) ); } /** * Get Day Appointments */ public static function get_day_appointments() { check_ajax_referer( 'bookit_day_appointments', 'nonce' ); $data = CleanHelper::cleanData( $_POST, self::getCleanRules() ); if ( empty( $data ) ) { wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) ); die(); } $appointments = Appointments::day_appointments( $data ); /** if google calendar addon is installed */ if ( Plugin::isAddonInstalledAndEnabled( self::$proAddon ) && has_filter( 'bookit_google_calendar_get_events_by_date' ) ) { $gcBusyTimeSlots = apply_filters( 'bookit_google_calendar_get_events_by_date', $data ); /** remove double values by id key */ foreach ( $gcBusyTimeSlots as $key => $value ) { if ( array_search( $value->id, array_column( $appointments, 'id' ) ) !== false || array_search( (string) $value->start_time, array_column( $appointments, 'start_time' ) ) ) { unset( $gcBusyTimeSlots[ $key ] ); } } $appointments = array_merge( $gcBusyTimeSlots, $appointments ); } /** if google calendar addon is installed | end */ wp_send_json_success( $appointments ); } /** * Check is Appointment time free */ public static function is_free_appointment() { check_ajax_referer( 'bookit_is_free_appointment', 'nonce' ); $data = CleanHelper::cleanData( $_POST, self::getCleanRules() ); if ( empty( $data['staff_id'] ) || empty( $data['service_id'] ) || empty( $data['date_timestamp'] ) || empty( $data['start_time'] ) || empty( $data['end_time'] ) ) { wp_send_json_error( array( 'message' => __( 'Error occurred!', 'bookit' ) ) ); } $appointment = Appointments::checkAppointment( $data ); wp_send_json_success( array( 'is_free' => false ) ); } }