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_migration_progress')); add_action('admin_notices', array($this, 'display_review_notice')); add_action('admin_post_almaweb_review_notice', array($this, 'handle_review_notice')); add_action('admin_notices', array($this, 'display_cache_notice')); add_action('admin_post_almaweb_cache_notice', array($this, 'handle_cache_notice')); add_action('admin_init', array($this, 'ensure_weekly_report_scheduled')); // 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, 'load_textdomain'), 0); add_action('init', array($this, 'track_visitor'), 1); add_action('almaweb_daily_cleanup', array($this, 'run_cleanup')); add_action('almaweb_weekly_report', array($this, 'run_weekly_report')); add_action('admin_menu', array($this, 'add_admin_menu')); add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets')); } /** * Charge le pack de langue WordPress.org s'il existe, puis fusionne les traductions embarquées * pour les chaînes que le pack ne contient pas encore (nouvelles versions). */ public function load_textdomain() { $domain = 'almaweb-ai-visitor-analytics'; load_plugin_textdomain($domain, false, dirname(ALMAWEB_PLUGIN_BASENAME) . '/languages'); $locale = function_exists('determine_locale') ? determine_locale() : get_locale(); $bundled = ALMAWEB_PLUGIN_DIR . 'languages/' . $domain . '-' . $locale . '.mo'; if (file_exists($bundled)) { load_textdomain($domain, $bundled, $locale); } } 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-schema-migrator.php'; require_once ALMAWEB_PLUGIN_DIR . 'includes/class-business-kpis.php'; require_once ALMAWEB_PLUGIN_DIR . 'includes/class-email-report.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 les tables avec la version la plus récente du schéma. // Sur une installation existante, seules les tables annexes sont créées : la migration pas à pas // (run_upgrade_routine) se charge du reste sans jamais marquer la version DB prématurément. $this->load_dependencies(); $this->create_complete_database_table(); $this->set_default_options(); $this->schedule_cleanup(); $this->ensure_weekly_report_scheduled(); // 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'); ?>
⭐

⭐
id, 'almaweb') === false) { return; } if (get_option('almaweb_cache_notice_dismissed')) { return; } $dismiss_url = wp_nonce_url(admin_url('admin-post.php?action=almaweb_cache_notice'), 'almaweb_cache_notice'); ?>
ℹ️
send_weekly_report(); } /** * Garantit que le cron du rapport hebdomadaire est planifié quand l'option est active. * Couvre la réactivation du plugin et les mises à jour automatiques, qui ne repassent * pas par la sauvegarde des réglages. */ public function ensure_weekly_report_scheduled() { $settings = get_option('almaweb_settings', []); if (empty($settings['report_enabled'])) { return; } if (wp_next_scheduled('almaweb_weekly_report')) { return; } $day = isset($settings['report_day']) ? max(0, min(6, intval($settings['report_day']))) : 1; $now = current_time('timestamp'); $diff = ($day - (int) gmdate('w', $now) + 7) % 7; $diff = $diff === 0 ? 7 : $diff; wp_schedule_event(strtotime("+{$diff} days midnight", $now), 'weekly', 'almaweb_weekly_report'); } /** * Crée les tables (nouvelles installations) ou les tables annexes (installations existantes). */ public function create_complete_database_table() { Almaweb_Schema_Migrator::create_fresh_schema(); } 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); } } /** * Mises à jour de schéma. La version DB n'est enregistrée qu'une fois toutes les étapes terminées : * une étape qui échoue ou qui manque de temps reprend au chargement admin suivant. */ public function run_upgrade_routine() { $db_version = get_option('almaweb_db_version', '1.0.0'); if (version_compare($db_version, ALMAWEB_VERSION, '>=')) { return; } 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(); } // La 1.2.2 (suppression de colonnes, types réduits) est entièrement couverte par la migration 1.3.2. if (version_compare($db_version, '1.3.2', '<')) { if (!Almaweb_Schema_Migrator::run()) { return; // en cours : reprise au prochain chargement admin } } update_option('almaweb_db_version', ALMAWEB_VERSION); } /** * Bandeau de progression pendant la migration 1.3.2 */ public function display_migration_progress() { if (version_compare(get_option('almaweb_db_version', '1.0.0'), '1.3.2', '>=')) { return; } $progress = Almaweb_Schema_Migrator::get_progress(); if (empty($progress['started'])) { return; } echo '

AlmaWeb AI Visitor Analytics — ' . esc_html(sprintf( /* translators: 1: current step number, 2: total number of steps. */ __('Database optimization in progress (step %1$d of %2$d). It continues automatically while you browse the admin and may take a few minutes.', 'almaweb-ai-visitor-analytics'), (int) $progress['step'], (int) $progress['total'] )) . '

'; } 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; $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`)"); } } } 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();