table_name = $wpdb->prefix . 'ai_monitor_logs'; register_activation_hook(__FILE__, array($this, 'activate')); register_deactivation_hook(__FILE__, array($this, 'deactivate')); add_action('plugins_loaded', array($this, 'init')); add_action('admin_init', array($this, 'run_upgrade_routine')); add_action('admin_notices', array($this, 'display_upgrade_errors')); add_action('admin_notices', array($this, 'display_review_notice')); add_action('admin_post_almaweb_review_notice', array($this, 'handle_review_notice')); // Pour les utilisateurs existants qui n'ont pas de date d'installation if (!get_option('almaweb_install_date')) { update_option('almaweb_install_date', time()); } } public function init() { $this->load_dependencies(); add_action('init', array($this, 'track_visitor'), 1); add_action('almaweb_daily_cleanup', array($this, 'run_cleanup')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } private function load_dependencies() { require_once ALMAWEB_PLUGIN_DIR . 'includes/class-ip-checker.php'; require_once ALMAWEB_PLUGIN_DIR . 'includes/class-bot-detector.php'; require_once ALMAWEB_PLUGIN_DIR . 'includes/class-referrer-detector.php'; require_once ALMAWEB_PLUGIN_DIR . 'includes/class-database.php'; require_once ALMAWEB_PLUGIN_DIR . 'includes/class-business-kpis.php'; require_once ALMAWEB_PLUGIN_DIR . 'admin/class-admin-dashboard.php'; require_once ALMAWEB_PLUGIN_DIR . 'admin/class-admin-settings.php'; } public function activate() { // Pour les nouvelles installations, on crée la table avec la version la plus récente du schéma $this->create_complete_database_table(); $this->set_default_options(); $this->schedule_cleanup(); // Enregistrer la date d'installation pour la notice de review if (!get_option('almaweb_install_date')) { update_option('almaweb_install_date', time()); } flush_rewrite_rules(); } /** * Affiche la notice d'invitation à laisser un avis */ public function display_review_notice() { // Seulement sur les pages du plugin $screen = get_current_screen(); if (!$screen || strpos($screen->id, 'almaweb') === false) { return; } $status = get_option('almaweb_review_notice_status'); if ($status === 'dismissed') { return; } $install_date = get_option('almaweb_install_date', time()); $remind_at = get_option('almaweb_review_remind_at', 0); // Pas encore 14 jours if (time() < $install_date + (14 * DAY_IN_SECONDS)) { return; } // En attente d'un rappel qui n'est pas encore échu if ($remind_at && time() < $remind_at) { return; } $review_url = 'https://wordpress.org/support/plugin/almaweb-ai-visitor-analytics/reviews/#new-post'; $dismiss_url = wp_nonce_url(admin_url('admin-post.php?action=almaweb_review_notice&response=dismissed'), 'almaweb_review_notice'); $later_url = wp_nonce_url(admin_url('admin-post.php?action=almaweb_review_notice&response=later'), 'almaweb_review_notice'); ?>
⭐

⭐
get_charset_collate(); $sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} ( id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, user_agent varchar(300) NOT NULL, ip_address varchar(45) NOT NULL, bot_name varchar(100) DEFAULT NULL, bot_type varchar(50) DEFAULT NULL, bot_family varchar(50) DEFAULT NULL, bot_intention varchar(50) DEFAULT NULL, is_ai_bot tinyint(1) DEFAULT 0, is_stealth tinyint(1) DEFAULT 0, detection_method varchar(20) DEFAULT 'user_agent', request_url varchar(1200) NOT NULL, referer varchar(500) DEFAULT NULL, action_type varchar(50) DEFAULT 'visit', page_visited varchar(500) DEFAULT NULL, http_accept varchar(200) DEFAULT NULL, http_accept_language varchar(100) DEFAULT NULL, visited_at datetime DEFAULT '0000-00-00 00:00:00', ai_referrer_source varchar(200) DEFAULT NULL, ai_referrer_type varchar(50) DEFAULT NULL, ai_referrer_family varchar(100) DEFAULT NULL, is_from_ai tinyint(1) DEFAULT 0, session_duration int(11) DEFAULT 0, pages_in_session int(11) DEFAULT 1, is_bounce tinyint(1) DEFAULT 1, is_return_visitor tinyint(1) DEFAULT 0, has_conversion tinyint(1) DEFAULT 0, conversion_type varchar(50) DEFAULT NULL, conversion_value decimal(10,2) DEFAULT NULL, is_blocked tinyint(1) DEFAULT 0, PRIMARY KEY (id), KEY ip_address (ip_address), KEY bot_name (bot_name), KEY is_ai_bot (is_ai_bot), KEY visited_at (visited_at), KEY bot_type (bot_type), KEY bot_family (bot_family), KEY bot_intention (bot_intention), KEY ai_referrer_source (ai_referrer_source), KEY ai_referrer_family (ai_referrer_family), KEY is_from_ai (is_from_ai), KEY is_blocked (is_blocked) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); update_option('almaweb_db_version', ALMAWEB_VERSION); } private function set_default_options() { if (!get_option('almaweb_settings')) { $default_options = ['track_enabled' => true, 'track_registered_users' => false, 'retention_days' => 90]; add_option('almaweb_settings', $default_options); } } public function run_upgrade_routine() { $db_version = get_option('almaweb_db_version', '1.0.0'); if (version_compare($db_version, ALMAWEB_VERSION, '<')) { if (version_compare($db_version, '1.1.0', '<')) { $this->run_manual_schema_update_to_1_1_0(); } if (version_compare($db_version, '1.2.0', '<')) { $this->run_manual_schema_update_to_1_2_0(); } if (version_compare($db_version, '1.2.2', '<')) { $this->run_manual_schema_update_to_1_2_2(); } update_option('almaweb_db_version', ALMAWEB_VERSION); } } public function display_upgrade_errors() { $error = get_transient('almaweb_upgrade_error'); if ($error) { echo '

AlmaWeb AI Visitor Analytics Upgrade Error:
' . esc_html($error) . '

'; delete_transient('almaweb_upgrade_error'); } } private function run_manual_schema_update_to_1_1_0() { global $wpdb; $table_name = $this->table_name; $wpdb->suppress_errors(false); $columns_to_add = [ 'ai_referrer_source' => "VARCHAR(200) NULL DEFAULT NULL", 'ai_referrer_type' => "VARCHAR(50) NULL DEFAULT NULL", 'ai_referrer_family' => "VARCHAR(100) NULL DEFAULT NULL", 'is_from_ai' => "TINYINT(1) NULL DEFAULT 0", 'session_id' => "VARCHAR(100) NULL DEFAULT NULL", 'session_duration' => "INT(11) NULL DEFAULT 0", 'pages_in_session' => "INT(11) NULL DEFAULT 1", 'is_bounce' => "TINYINT(1) NULL DEFAULT 1", 'is_return_visitor' => "TINYINT(1) NULL DEFAULT 0", 'has_conversion' => "TINYINT(1) NULL DEFAULT 0", 'conversion_type' => "VARCHAR(50) NULL DEFAULT NULL", 'conversion_value' => "DECIMAL(10,2) NULL DEFAULT NULL" ]; $raw_columns_query = $wpdb->get_results("DESCRIBE {$table_name}"); if (!$raw_columns_query) { return; } $existing_columns = wp_list_pluck($raw_columns_query, 'Field'); foreach ($columns_to_add as $column_name => $column_definition) { if (!in_array($column_name, $existing_columns)) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $column_name and $column_definition are hardcoded in this method, not user input $wpdb->query("ALTER TABLE {$table_name} ADD COLUMN `" . esc_sql($column_name) . "` {$column_definition}"); if (!empty($wpdb->last_error)) { set_transient('almaweb_upgrade_error', "Failed to add column '{$column_name}'. Error: " . $wpdb->last_error, 120); return; } } } $keys_to_add = ['ai_referrer_source', 'ai_referrer_family', 'is_from_ai', 'session_id', 'has_conversion']; foreach ($keys_to_add as $key_name) { $key_exists = $wpdb->get_var($wpdb->prepare("SHOW INDEX FROM {$table_name} WHERE Key_name = %s", $key_name)); if (!$key_exists) { $wpdb->query("ALTER TABLE {$table_name} ADD KEY `{$key_name}` (`{$key_name}`)"); if (!empty($wpdb->last_error)) { set_transient('almaweb_upgrade_error', "Failed to add index '{$key_name}'. Error: " . $wpdb->last_error, 120); return; } } } } private function run_manual_schema_update_to_1_2_0() { global $wpdb; $table_name = $this->table_name; $raw_columns_query = $wpdb->get_results("DESCRIBE {$table_name}"); if (!$raw_columns_query) { return; } $existing_columns = wp_list_pluck($raw_columns_query, 'Field'); if (!in_array('is_blocked', $existing_columns)) { $wpdb->query("ALTER TABLE {$table_name} ADD COLUMN `is_blocked` TINYINT(1) NULL DEFAULT 0"); $key_exists = $wpdb->get_var($wpdb->prepare("SHOW INDEX FROM {$table_name} WHERE Key_name = %s", 'is_blocked')); if (!$key_exists) { $wpdb->query("ALTER TABLE {$table_name} ADD KEY `is_blocked` (`is_blocked`)"); } } } private function run_manual_schema_update_to_1_2_2() { global $wpdb; $table_name = $this->table_name; // Supprimer les colonnes inutiles $columns_to_drop = ['country', 'city', 'time_spent', 'session_id', 'http_accept_encoding', 'request_method']; $raw_columns_query = $wpdb->get_results("DESCRIBE {$table_name}"); if (!$raw_columns_query) { return; } $existing_columns = wp_list_pluck($raw_columns_query, 'Field'); foreach ($columns_to_drop as $col) { if (in_array($col, $existing_columns)) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- column name is hardcoded $wpdb->query("ALTER TABLE {$table_name} DROP COLUMN `{$col}`"); if (!empty($wpdb->last_error)) { set_transient('almaweb_upgrade_error', "Failed to drop column '{$col}': " . $wpdb->last_error, 120); return; } } } // Réduire les tailles et convertir TEXT -> VARCHAR $alterations = [ 'user_agent' => "varchar(300) NOT NULL DEFAULT ''", 'ip_address' => "varchar(45) NOT NULL DEFAULT ''", 'bot_name' => "varchar(100) DEFAULT NULL", 'bot_type' => "varchar(50) DEFAULT NULL", 'bot_family' => "varchar(50) DEFAULT NULL", 'detection_method' => "varchar(20) DEFAULT 'user_agent'", 'request_url' => "varchar(1200) NOT NULL DEFAULT ''", 'referer' => "varchar(500) DEFAULT NULL", 'page_visited' => "varchar(500) DEFAULT NULL", 'http_accept' => "varchar(200) DEFAULT NULL", ]; foreach ($alterations as $col => $definition) { if (in_array($col, $existing_columns)) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $col and $definition are hardcoded in this method, not user input $wpdb->query("ALTER TABLE {$table_name} MODIFY COLUMN `" . esc_sql($col) . "` {$definition}"); if (!empty($wpdb->last_error)) { set_transient('almaweb_upgrade_error', "Failed to alter column '{$col}': " . $wpdb->last_error, 120); return; } } } // Supprimer les index devenus inutiles $indexes_to_drop = ['is_stealth', 'detection_method', 'session_id', 'has_conversion']; foreach ($indexes_to_drop as $idx) { $key_exists = $wpdb->get_var($wpdb->prepare("SHOW INDEX FROM {$table_name} WHERE Key_name = %s", $idx)); if ($key_exists) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- index name is hardcoded $wpdb->query("ALTER TABLE {$table_name} DROP INDEX `{$idx}`"); } } } public function track_visitor() { $settings = get_option('almaweb_settings'); if (empty($settings['track_enabled']) || ((defined('DOING_AJAX') && DOING_AJAX) || is_admin()) || (empty($settings['track_registered_users']) && is_user_logged_in())) { return; } // Ne pas logger les appels wp-cron (aucune valeur analytique) $request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''; if (strpos($request_uri, 'wp-cron.php') !== false || (defined('DOING_CRON') && DOING_CRON)) { return; } $bot_detector = new Almaweb_Bot_Detector(); $visitor_data = $bot_detector->analyze_request(); $ai_referrer = false; $referrer_detector = new Almaweb_Referrer_Detector(); // 1. Détection via utm_source (prioritaire) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tracking parameter, read-only $utm_source = isset($_GET['utm_source']) ? sanitize_text_field(wp_unslash($_GET['utm_source'])) : ''; if (!empty($utm_source)) { $ai_referrer = $referrer_detector->detect_ai_from_utm_source($utm_source); } // 2. Si non détecté via utm_source, détection via HTTP_REFERER if (!$ai_referrer) { $referer = isset($_SERVER['HTTP_REFERER']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_REFERER'])) : ''; if (!empty($referer)) { $ai_referrer = $referrer_detector->detect_ai_referrer($referer); } } // Si un référent AI est détecté, enrichir les données du visiteur if ($ai_referrer) { $visitor_data = array_merge($visitor_data, [ 'ai_referrer_source' => $ai_referrer['name'], 'ai_referrer_type' => $ai_referrer['type'], 'ai_referrer_family' => $ai_referrer['family'], 'is_from_ai' => true, ]); } // Firewall: Check if bot should be blocked $is_blocked = $this->should_block_bot($visitor_data, $settings); $visitor_data['is_blocked'] = $is_blocked ? 1 : 0; $database = new Almaweb_Database(); $database->log_visit($visitor_data); // Block after logging (so we keep the data) if ($is_blocked) { status_header(403); exit; } } public function add_admin_menu() { add_menu_page(__('AlmaWeb AI Visitor Analytics', 'almaweb-ai-visitor-analytics'), __('AI Analytics', 'almaweb-ai-visitor-analytics'), 'manage_options', 'almaweb-ai-visitor-analytics', array($this, 'render_dashboard'), 'dashicons-visibility', 30); add_submenu_page('almaweb-ai-visitor-analytics', __('Dashboard', 'almaweb-ai-visitor-analytics'), __('Dashboard', 'almaweb-ai-visitor-analytics'), 'manage_options', 'almaweb-ai-visitor-analytics', array($this, 'render_dashboard')); // Sous-menu Sources IA add_submenu_page( 'almaweb-ai-visitor-analytics', __('AI Sources', 'almaweb-ai-visitor-analytics'), __('AI Sources', 'almaweb-ai-visitor-analytics'), 'manage_options', 'almaweb-ai-visitor-analytics-referrers', array($this, 'render_referrers') ); add_submenu_page('almaweb-ai-visitor-analytics', __('Logs', 'almaweb-ai-visitor-analytics'), __('Logs', 'almaweb-ai-visitor-analytics'), 'manage_options', 'almaweb-ai-visitor-analytics-logs', array($this, 'render_logs')); add_submenu_page('almaweb-ai-visitor-analytics', __('Settings', 'almaweb-ai-visitor-analytics'), __('Settings', 'almaweb-ai-visitor-analytics'), 'manage_options', 'almaweb-ai-visitor-analytics-settings', array($this, 'render_settings')); } public function enqueue_admin_assets($hook) { if (strpos($hook, 'almaweb-ai-visitor-analytics') === false) return; wp_enqueue_style('almaweb-ai-visitor-analytics-admin', ALMAWEB_PLUGIN_URL . 'assets/css/admin.css', array(), ALMAWEB_VERSION); wp_enqueue_script('chartjs', ALMAWEB_PLUGIN_URL . 'assets/js/vendor/chart.min.js', array(), '4.5.1', true); wp_enqueue_script('almaweb-ai-visitor-analytics-admin', ALMAWEB_PLUGIN_URL . 'assets/js/admin.js', array('chartjs'), ALMAWEB_VERSION, true); } public function render_dashboard() { $dashboard = new Almaweb_Admin_Dashboard(); $dashboard->render(); $this->render_footer(); } public function render_logs() { require_once ALMAWEB_PLUGIN_DIR . 'admin/views/logs.php'; $this->render_footer(); } public function render_settings() { $settings = new Almaweb_Admin_Settings(); $settings->render(); $this->render_footer(); } public function render_referrers() { require_once ALMAWEB_PLUGIN_DIR . 'admin/views/referrers.php'; $this->render_footer(); } public function render_footer() { /* translators: %s: Link to AlmaWeb website. */ echo ''; } private function schedule_cleanup() { if (!wp_next_scheduled('almaweb_daily_cleanup')) wp_schedule_event(time(), 'daily', 'almaweb_daily_cleanup'); } private function should_block_bot($visitor_data, $settings) { $blocked_intentions = $settings['blocked_intentions'] ?? []; $blocked_families = $settings['blocked_families'] ?? []; if (empty($blocked_intentions) && empty($blocked_families)) { return false; } if (!empty($visitor_data['bot_intention']) && in_array($visitor_data['bot_intention'], $blocked_intentions, true)) { return true; } if (!empty($visitor_data['bot_family']) && in_array($visitor_data['bot_family'], $blocked_families, true)) { return true; } return false; } public function run_cleanup() { $settings = get_option('almaweb_settings'); $retention_days = isset($settings['retention_days']) ? intval($settings['retention_days']) : 90; $database = new Almaweb_Database(); $database->cleanup_old_logs($retention_days); } } function almaweb_ai_visitor_analytics() { return almaweb_ai_visitor_analytics::get_instance(); } almaweb_ai_visitor_analytics();