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 );
}
}