plugin_url = plugin_dir_url(dirname(__FILE__)); $this->init(); } public function init() { // Check if frontend widget is enabled if ($this->is_widget_enabled()) { add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_scripts']); add_action('wp_footer', [$this, 'render_widget_template']); } } /** * Check if frontend widget is enabled */ private function is_widget_enabled() { global $wpdb; $table_settings = $wpdb->prefix . 'acb_settings'; // Check if settings table exists if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table_settings)) !== $table_settings) { return true; // Default to enabled if table doesn't exist } $enabled = $wpdb->get_var( $wpdb->prepare("SELECT setting_value FROM {$wpdb->prefix}acb_settings WHERE setting_key = %s", 'frontend_widget_enabled') ); // Handle boolean conversion - default to true if not set if ($enabled !== null) { return ($enabled === '1' || $enabled === 'true' || $enabled === true || $enabled === 1); } return true; // Default to enabled } /** * Enqueue frontend scripts and styles */ public function enqueue_frontend_scripts() { // Only load on frontend pages if (is_admin()) { return; } // Enqueue frontend widget styles wp_enqueue_style( 'acb-frontend-widget-style', esc_url($this->plugin_url . 'build/frontend-index.css'), [], '1.0.0' ); wp_enqueue_style( 'font-awesome', esc_url($this->plugin_url . 'assets/library/all.min.css'), [], '5.15.4' ); // Enqueue frontend widget script wp_enqueue_script( 'acb-frontend-widget-script', esc_url($this->plugin_url . 'build/frontend-index.js'), ['jquery', 'wp-element'], '1.0.0', true ); // Pass Firebase config and nonces to frontend $firebase_config = get_option('firebaseConfig', []); // Sanitize Firebase config before passing to frontend $sanitized_config = $this->sanitize_firebase_config($firebase_config); // Keep the same variable names for JavaScript compatibility wp_localize_script('acb-frontend-widget-script', 'appLocalizer', [ 'apiUrl' => home_url( '/wp-json' ), 'nonce' => wp_create_nonce('acb_nonce'), 'adminAjax' => admin_url('admin-ajax.php'), 'config' => $sanitized_config, ]); } /** * Sanitize Firebase config for frontend use */ private function sanitize_firebase_config($config) { if (!is_array($config)) { return []; } $sanitized = []; $allowed_keys = ['apiKey', 'authDomain', 'projectId', 'storageBucket', 'messagingSenderId', 'appId']; foreach ($allowed_keys as $key) { if (isset($config[$key])) { $sanitized[$key] = sanitize_text_field($config[$key]); } } return $sanitized; } /** * Render widget template in footer */ public function render_widget_template() { // Only render if widget is enabled if (!$this->is_widget_enabled()) { return; } // Check if widget should be displayed on current page if (!$this->should_display_widget()) { return; } // Get widget settings from database $widget_settings = $this->get_widget_settings_from_db(); ?>
prefix . 'acb_settings'; $settings_keys = [ 'widget_position', 'widget_title', 'widget_description', 'widget_primary_color' ]; $defaults = [ 'widget_position' => 'bottom-right', 'widget_title' => 'Chat Support', 'widget_description' => 'Need help? Chat with our team!', 'widget_primary_color' => '#5A5EB9' ]; $widget_settings = []; foreach ($settings_keys as $key) { $value = $wpdb->get_var( $wpdb->prepare("SELECT setting_value FROM {$wpdb->prefix}acb_settings WHERE setting_key = %s", $key) ); $widget_settings[$key] = $value !== null ? $value : $defaults[$key]; } // Validate position $allowed_positions = ['bottom-right', 'bottom-left', 'top-right', 'top-left']; if (!in_array($widget_settings['widget_position'], $allowed_positions, true)) { $widget_settings['widget_position'] = 'bottom-right'; } return $widget_settings; } /** * Get widget settings for admin */ public static function get_widget_settings() { return [ 'enabled' => (bool) get_option('acb_frontend_widget_enabled', false), 'position' => sanitize_text_field(get_option('acb_widget_position', 'bottom-right')), 'title' => sanitize_text_field(get_option('acb_widget_title', 'Chat Support')), 'description' => sanitize_text_field(get_option('acb_widget_description', 'Need help? Chat with our team!')), 'show_on_pages' => sanitize_text_field(get_option('acb_widget_show_on_pages', 'all')), 'exclude_pages' => sanitize_textarea_field(get_option('acb_widget_exclude_pages', '')), ]; } /** * Save widget settings with proper validation and sanitization */ public static function save_widget_settings($settings) { // Issue 3.9: Verify nonce using the unified acb_nonce action. if ( wp_doing_ajax() ) { if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'acb_nonce' ) ) { wp_send_json_error( [ 'message' => __( 'Security check failed.','admin-chat-box' ) ] ); wp_die(); } } // Issue 3.10: Wrap capability check so it only sends JSON in AJAX context. if ( ! current_user_can( 'manage_options' ) ) { if ( wp_doing_ajax() ) { wp_send_json_error( [ 'message' => __( 'Insufficient permissions.','admin-chat-box' ) ] ); wp_die(); } return false; } // Issue 3.10: Wrap format check so it only sends JSON in AJAX context. if ( ! is_array( $settings ) ) { if ( wp_doing_ajax() ) { wp_send_json_error( [ 'message' => __( 'Invalid settings format.','admin-chat-box' ) ] ); wp_die(); } return false; } // Sanitize enabled setting $enabled = isset($settings['enabled']) ? (bool) $settings['enabled'] : false; update_option('acb_frontend_widget_enabled', $enabled); // Sanitize and validate position $position = sanitize_text_field($settings['position'] ?? 'bottom-right'); $allowed_positions = ['bottom-right', 'bottom-left', 'top-right', 'top-left']; if (!in_array($position, $allowed_positions, true)) { $position = 'bottom-right'; } update_option('acb_widget_position', $position); // Sanitize text fields $title = sanitize_text_field($settings['title'] ?? 'Chat Support'); $title = wp_unslash($title); // Remove slashes if any if (strlen($title) > 100) { // Limit title length $title = substr($title, 0, 100); } update_option('acb_widget_title', $title); $description = sanitize_textarea_field($settings['description'] ?? 'Need help? Chat with our team!'); $description = wp_unslash($description); // Remove slashes if any if (strlen($description) > 500) { // Limit description length $description = substr($description, 0, 500); } update_option('acb_widget_description', $description); // Sanitize show_on_pages $show_on_pages = sanitize_text_field($settings['show_on_pages'] ?? 'all'); $allowed_show_options = ['all', 'specific', 'exclude']; if (!in_array($show_on_pages, $allowed_show_options, true)) { $show_on_pages = 'all'; } update_option('acb_widget_show_on_pages', $show_on_pages); // Sanitize exclude_pages $exclude_pages = sanitize_textarea_field($settings['exclude_pages'] ?? ''); $exclude_pages = wp_unslash($exclude_pages); // Remove slashes if any // Validate page IDs if provided if (!empty($exclude_pages)) { $page_ids = array_map('trim', explode(',', $exclude_pages)); $valid_page_ids = []; foreach ($page_ids as $page_id) { $page_id = absint($page_id); // Ensure positive integer if ($page_id > 0) { $valid_page_ids[] = $page_id; } } $exclude_pages = implode(',', $valid_page_ids); } update_option('acb_widget_exclude_pages', $exclude_pages); // Send success response for AJAX requests if (wp_doing_ajax()) { wp_send_json_success(['message' => 'Settings saved successfully']); } return true; } /** * Check if widget should be displayed on current page */ private function should_display_widget() { global $wpdb; $table_settings = $wpdb->prefix . 'acb_settings'; $show_on_pages = $wpdb->get_var( $wpdb->prepare("SELECT setting_value FROM {$wpdb->prefix}acb_settings WHERE setting_key = %s", 'widget_show_on_pages') ); $exclude_pages = $wpdb->get_var( $wpdb->prepare("SELECT setting_value FROM {$wpdb->prefix}acb_settings WHERE setting_key = %s", 'widget_exclude_pages') ); $show_on_pages = $show_on_pages !== null ? $show_on_pages : 'all'; $exclude_pages = $exclude_pages !== null ? $exclude_pages : ''; $current_page_id = get_the_ID(); // If excluding specific pages if (!empty($exclude_pages) && $current_page_id) { $excluded_ids = array_map('absint', explode(',', $exclude_pages)); if (in_array($current_page_id, $excluded_ids, true)) { return false; } } // Additional display logic can be added here based on show_on_pages setting return true; } /** * AJAX handler for widget settings — Issue 3.7: nonce verified before reading $_POST. */ public static function ajax_save_widget_settings() { // Verify nonce BEFORE reading any POST data. if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'acb_nonce' ) ) { wp_send_json_error( [ 'message' => __( 'Security check failed.','admin-chat-box' ) ] ); wp_die(); } if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => __( 'Insufficient permissions.','admin-chat-box' ) ] ); wp_die(); } $settings = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized self::save_widget_settings( $settings ); } }