'',
'enable_embed' => false,
'embed_code' => '',
'enable_woocommerce_sync' => false
);
/**
* Get all settings
*/
public static function get_settings() {
$settings = array();
// Get each setting individually
foreach (self::$setting_keys as $key) {
$option_name = self::OPTION_PREFIX . $key;
$default = isset(self::$default_settings[$key]) ? self::$default_settings[$key] : null;
$settings[$key] = get_option($option_name, $default);
}
return $settings;
}
/**
* Get specific setting
*/
public static function get_setting($key, $default = null) {
$option_name = self::OPTION_PREFIX . $key;
// Use provided default or get from defaults array
if ($default === null && isset(self::$default_settings[$key])) {
$default = self::$default_settings[$key];
}
return get_option($option_name, $default);
}
/**
* Update all settings
*/
public static function update_settings($settings) {
$validated = self::validate_settings($settings);
$updated = true;
// Update each setting individually
foreach ($validated as $key => $value) {
$option_name = self::OPTION_PREFIX . $key;
$result = update_option($option_name, $value);
// If any update fails, mark the overall update as failed
if (!$result) {
$updated = false;
}
}
return $updated;
}
/**
* Update a single setting
*/
public static function update_setting($key, $value) {
// Validate single setting
$validated = self::validate_settings(array($key => $value));
if (isset($validated[$key])) {
$option_name = self::OPTION_PREFIX . $key;
return update_option($option_name, $validated[$key]);
}
return false;
}
/**
* Delete settings
*/
public static function delete_settings() {
$deleted = true;
// Delete each setting individually
foreach (self::$setting_keys as $key) {
$option_name = self::OPTION_PREFIX . $key;
$result = delete_option($option_name);
// If any deletion fails, mark the overall deletion as failed
if (!$result) {
$deleted = false;
}
}
return $deleted;
}
/**
* Reset settings to default
*/
public static function reset_settings() {
return self::update_settings(self::$default_settings);
}
/**
* Validate settings
*/
public static function validate_settings($settings) {
$validated = array();
// Validate API key
if (isset($settings['api_key'])) {
$validated['api_key'] = sanitize_text_field($settings['api_key']);
}
// Validate enable embed
if (isset($settings['enable_embed'])) {
$validated['enable_embed'] = (bool) $settings['enable_embed'];
}
// Validate embed code - safely handle script tags
if (isset($settings['embed_code'])) {
$validated['embed_code'] = self::sanitize_embed_code($settings['embed_code']);
}
// Validate enable_woocommerce_sync
if (isset($settings['enable_woocommerce_sync'])) {
$validated['enable_woocommerce_sync'] = (bool) $settings['enable_woocommerce_sync'];
}
// Validate website_id
if (isset($settings['website_id'])) {
$validated['website_id'] = sanitize_text_field($settings['website_id']);
}
return $validated;
}
/**
* Public method to validate settings (wrapper for private validate_settings)
*/
public static function sanitize_settings($settings) {
return self::validate_settings($settings);
}
/**
* Safely sanitize embed code for Chatweb script tags
*
* @param string $embed_code Raw embed code from API
* @return string Sanitized embed code
*/
public static function sanitize_embed_code($embed_code) {
// Ensure it's a string
if (!is_string($embed_code)) {
return '';
}
// Trim whitespace
$embed_code = trim($embed_code);
// Check if empty
if (empty($embed_code)) {
return '';
}
// Validate that it's a Chatweb script tag
if (!self::is_valid_aiflab_chatweb_embed($embed_code)) {
return '';
}
// Return sanitized embed code (WordPress handles escaping automatically)
return $embed_code;
}
/**
* Validate that embed code is a valid Chatweb script tag
*
* @param string $embed_code
* @return bool
*/
private static function is_valid_aiflab_chatweb_embed($embed_code) {
// Expected pattern:
// Basic checks
if (!str_contains($embed_code, '') !== false,
'has_widget_chatweb' => (strpos($embed_code, 'widget.chatweb.io') !== false || strpos($embed_code, 'widget-dev.chatweb.to') !== false || strpos($embed_code, 'chatweb.io') !== false || strpos($embed_code, 'chatweb.to') !== false),
'has_data_token' => strpos($embed_code, 'data-token') !== false
));
return false;
}
// Lưu vào CẢ HAI options để đảm bảo có giá trị
// Method 1: Lưu vào option chính
$option_name = self::OPTION_PREFIX . 'embed_code';
$option_name_b64 = self::OPTION_PREFIX . 'embed_code_b64';
// Clear cache trước khi lưu
wp_cache_delete($option_name, 'options');
wp_cache_delete($option_name_b64, 'options');
// Use $wpdb directly to ensure options are created/updated
global $wpdb;
// Check if options exist in database (not from cache)
$option_exists = $wpdb->get_var($wpdb->prepare(
"SELECT option_id FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
$option_name
));
$option_b64_exists = $wpdb->get_var($wpdb->prepare(
"SELECT option_id FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
$option_name_b64
));
// Encode to base64 for backup
$encoded_embed_code = base64_encode($sanitized_embed_code);
// Log before saving
self::log_embed_code_debug('before_save', array(
'option_exists' => !empty($option_exists),
'option_b64_exists' => !empty($option_b64_exists),
'sanitized_length' => strlen($sanitized_embed_code),
'encoded_length' => strlen($encoded_embed_code)
));
// Save main option using $wpdb directly
$result_main = false;
$wpdb_error_main = '';
if ($option_exists) {
// Update existing option
$update_result = $wpdb->update(
$wpdb->options,
array('option_value' => $sanitized_embed_code),
array('option_name' => $option_name),
array('%s'),
array('%s')
);
$result_main = ($update_result !== false);
if ($update_result === false && !empty($wpdb->last_error)) {
$wpdb_error_main = $wpdb->last_error;
}
} else {
// Insert new option
$insert_result = $wpdb->insert(
$wpdb->options,
array(
'option_name' => $option_name,
'option_value' => $sanitized_embed_code,
'autoload' => 'no'
),
array('%s', '%s', '%s')
);
$result_main = ($insert_result !== false);
if ($insert_result === false && !empty($wpdb->last_error)) {
$wpdb_error_main = $wpdb->last_error;
}
}
// Save base64 option using $wpdb directly
$result_b64 = false;
$wpdb_error_b64 = '';
if ($option_b64_exists) {
// Update existing option
$update_result = $wpdb->update(
$wpdb->options,
array('option_value' => $encoded_embed_code),
array('option_name' => $option_name_b64),
array('%s'),
array('%s')
);
$result_b64 = ($update_result !== false);
if ($update_result === false && !empty($wpdb->last_error)) {
$wpdb_error_b64 = $wpdb->last_error;
}
} else {
// Insert new option
$insert_result = $wpdb->insert(
$wpdb->options,
array(
'option_name' => $option_name_b64,
'option_value' => $encoded_embed_code,
'autoload' => 'no'
),
array('%s', '%s', '%s')
);
$result_b64 = ($insert_result !== false);
if ($insert_result === false && !empty($wpdb->last_error)) {
$wpdb_error_b64 = $wpdb->last_error;
}
}
// Log save results
self::log_embed_code_debug('save_results', array(
'result_main' => $result_main,
'result_b64' => $result_b64,
'wpdb_error_main' => $wpdb_error_main,
'wpdb_error_b64' => $wpdb_error_b64,
'option_exists_before' => !empty($option_exists),
'option_b64_exists_before' => !empty($option_b64_exists)
));
// Verify what was saved immediately by querying database directly
wp_cache_delete($option_name, 'options');
wp_cache_delete($option_name_b64, 'options');
// Query database directly to verify (bypass cache)
$saved_code = $wpdb->get_var($wpdb->prepare(
"SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
$option_name
));
$saved_b64 = $wpdb->get_var($wpdb->prepare(
"SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
$option_name_b64
));
// If main option is empty but base64 is saved, try to restore from base64
if (empty($saved_code) && !empty($saved_b64)) {
// Decode from base64 and verify
$decoded = base64_decode($saved_b64);
if (!empty($decoded) && $decoded === $sanitized_embed_code) {
// Try using $wpdb directly to bypass WordPress filters
// Check if option exists
$option_exists_check = $wpdb->get_var($wpdb->prepare(
"SELECT option_id FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
$option_name
));
if ($option_exists_check) {
// Update existing option
$wpdb->update(
$wpdb->options,
array('option_value' => $sanitized_embed_code),
array('option_name' => $option_name),
array('%s'),
array('%s')
);
} else {
// Insert new option
$wpdb->insert(
$wpdb->options,
array(
'option_name' => $option_name,
'option_value' => $sanitized_embed_code,
'autoload' => 'no'
),
array('%s', '%s', '%s')
);
}
wp_cache_delete($option_name, 'options');
// Re-query to verify
$saved_code = $wpdb->get_var($wpdb->prepare(
"SELECT option_value FROM {$wpdb->options} WHERE option_name = %s LIMIT 1",
$option_name
));
}
}
// Return success if at least one method succeeded
// Check if values match what we tried to save
$main_success = (!empty($saved_code) && $saved_code === $sanitized_embed_code);
$b64_success = (!empty($saved_b64) && base64_decode($saved_b64) === $sanitized_embed_code);
return $main_success || $b64_success;
}
/**
* Clear embed code from database
*/
public static function clear_embed_code() {
$option_name = self::OPTION_PREFIX . 'embed_code';
$option_name_b64 = self::OPTION_PREFIX . 'embed_code_b64';
// Clear cache
wp_cache_delete($option_name, 'options');
wp_cache_delete($option_name_b64, 'options');
// Delete both options
$result1 = delete_option($option_name);
$result2 = delete_option($option_name_b64);
return $result1 || $result2;
}
/**
* Log embed code operations for debugging (static method)
*
* @param string $log_type Type of log entry
* @param mixed $log_data Data to log
* @return bool Success or failure
*/
private static function log_embed_code_debug($log_type, $log_data) {
try {
// Direct file logging (no dependency on Aiflab_Chatweb_API instance)
$upload_dir = wp_upload_dir();
if (isset($upload_dir['error']) && $upload_dir['error'] !== false) {
return false;
}
$logs_dir = $upload_dir['basedir'] . '/chatweb-logs';
if (!file_exists($logs_dir)) {
$dir_created = wp_mkdir_p($logs_dir);
if (!$dir_created) {
return false;
}
// Create .htaccess to protect logs
$htaccess_file = $logs_dir . '/.htaccess';
$htaccess_content = "Order deny,allow\nDeny from all";
@file_put_contents($htaccess_file, $htaccess_content);
// Create index.php to prevent directory listing
$index_file = $logs_dir . '/index.php';
$index_content = ' gmdate('Y-m-d H:i:s'),
'type' => $log_type,
'data' => $log_data
);
// Write to log file with pretty print
$log_line = json_encode($log_entry, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n---\n";
$bytes_written = @file_put_contents($log_file, $log_line, FILE_APPEND);
return $bytes_written !== false;
} catch (Exception $e) {
// Catch any unexpected exceptions to prevent disrupting the main process
return false;
}
}
/**
* Alternative method: Save embed code as base64 to bypass WordPress filtering
*/
public static function set_embed_code_base64($embed_code) {
// Sanitize first
$sanitized_embed_code = self::sanitize_embed_code($embed_code);
if (empty($sanitized_embed_code)) {
return false;
}
// Encode to base64
$encoded_embed_code = base64_encode($sanitized_embed_code);
// Save with different option name
$option_name = self::OPTION_PREFIX . 'embed_code_b64';
$result = update_option($option_name, $encoded_embed_code, false);
// Verify
$saved_encoded = get_option($option_name, '');
if (!empty($saved_encoded)) {
$decoded = base64_decode($saved_encoded);
// Also save to regular option for compatibility
update_option(self::OPTION_PREFIX . 'embed_code', $decoded, false);
return true;
}
return false;
}
/**
* Get embed code with base64 fallback (no infinite loop)
*/
public static function get_embed_code_with_fallback() {
// Try base64 method directly (don't call get_embed_code())
$option_name = self::OPTION_PREFIX . 'embed_code_b64';
$encoded_embed_code = get_option($option_name, '');
if (!empty($encoded_embed_code)) {
$decoded = base64_decode($encoded_embed_code);
// Validate decoded content
if (self::is_valid_aiflab_chatweb_embed($decoded)) {
return $decoded;
}
}
return '';
}
/**
* Clear embed code (alias for clear_embed_code)
*/
public static function clear_cache() {
return self::clear_embed_code();
}
/**
* Get plugin info
*/
public static function get_plugin_info() {
return array(
'name' => 'AIFusionLab Chatbot for Chatweb',
'version' => AIFLAB_CHATWEB_PLUGIN_VERSION,
'url' => AIFLAB_CHATWEB_PLUGIN_URL,
'path' => AIFLAB_CHATWEB_PLUGIN_PATH,
'basename' => AIFLAB_CHATWEB_PLUGIN_BASENAME
);
}
/**
* Hook into WordPress settings API to handle our settings
* Note: Removed embed_code hook to prevent infinite loops
*/
public static function init_settings_hooks() {
// Hook into each individual setting to update our config
add_action('update_option_aiflab_chatweb_api_key', function($old_value, $new_value) {
self::update_setting('api_key', $new_value);
}, 10, 2);
add_action('update_option_aiflab_chatweb_enable_embed', function($old_value, $new_value) {
self::update_setting('enable_embed', $new_value);
}, 10, 2);
// Removed embed_code hook to prevent conflicts with set_embed_code method
add_action('update_option_aiflab_chatweb_enable_woocommerce_sync', function($old_value, $new_value) {
self::update_setting('enable_woocommerce_sync', $new_value);
}, 10, 2);
add_action('update_option_aiflab_chatweb_website_id', function($old_value, $new_value) {
self::update_setting('website_id', $new_value);
}, 10, 2);
}
/**
* Clear caches from popular caching plugins
*/
public static function clear_plugin_caches() {
// WP Super Cache
if (function_exists('wp_cache_clear_cache')) {
wp_cache_clear_cache();
}
// W3 Total Cache
if (function_exists('w3tc_flush_all')) {
w3tc_flush_all();
}
// WP Rocket
if (function_exists('rocket_clean_domain')) {
rocket_clean_domain();
}
// WP Fastest Cache
if (class_exists('WpFastestCache')) {
$wpfc = new WpFastestCache();
if (method_exists($wpfc, 'deleteCache')) {
$wpfc->deleteCache(true);
}
}
// LiteSpeed Cache
if (class_exists('LiteSpeed_Cache_API') && method_exists('LiteSpeed_Cache_API', 'purge_all')) {
LiteSpeed_Cache_API::purge_all();
}
// Autoptimize
if (class_exists('autoptimizeCache') && method_exists('autoptimizeCache', 'clearall')) {
autoptimizeCache::clearall();
}
}
}