ai_app_onsite_generate_random_time(); wp_schedule_single_event($random_time, 'ai_app_onsite_user_stats_cron'); } } public function ai_app_onsite_generate_random_time() { $dt = new DateTime('tomorrow midnight', new DateTimeZone('UTC')); $start = $dt->getTimestamp() - 10; // 23:59:50 UTC, 10 sec before midnight $end = $start + (15 * 60); // 00:14:50 UTC, 900 sec/15 min after midnight return rand($start, $end); } /** * Save user statistics after AI response * * @param array $result The AI response result. * @param string $prompt The original prompt sent to the AI. * @param array $formData Additional form data submitted with the request. * @param string $response_content The content of the AI's response. * @return string The response ID of the saved statistics. */ public function ai_app_onsite_save_user_stats($result, $prompt, $formData,$response_content): string { global $wpdb; $app_selector = ai_app_onsite_get_app_selector(); if (!$app_selector) { wp_send_json_error('App not found. Please create an app first.'); exit; } // Fallback: Get IP using PHP $user_ip = 'Unknown'; // Optional fallback message $user_geolocation = wp_json_encode(array( 'ip' => $user_ip, 'source' => 'fallback-php', 'error' => '' )); // Extract individual settings values $formData = isset($formData) ? wp_json_encode($formData) : ''; $used_token = isset($result['usage']['total_tokens']) ? sanitize_text_field($result['usage']['total_tokens']) : ''; $response_Id = isset($result['id']) ? sanitize_text_field($result['id']) : ''; $prompt = isset($prompt) ? sanitize_text_field($prompt) : ''; // Check if the model is reasoning or not $table_name_models = $wpdb->prefix . 'ai_app_onsite_models'; $table_name_model_settings = $wpdb->prefix . 'ai_app_onsite_model_settings'; $model_settings = $wpdb->get_results( $wpdb->prepare("SELECT * FROM $table_name_model_settings WHERE app_id = %s", $app_selector), ARRAY_A ); if (empty($model_settings)) { wp_send_json_error('Plugin Model settings are missing'); } $model_parameters = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $table_name_models WHERE model = %s LIMIT 1", $model_settings[0]['model']), ARRAY_A ); if (empty($model_parameters)) { wp_send_json_error('Model parameters not found for the selected model'); } // Insert new row $table_name_stats = $wpdb->prefix . 'ai_app_onsite_user_statistics'; $wpdb->insert( $table_name_stats, array( 'app_id' => 1, 'response_id' => $response_Id, 'user_ip' => $user_ip, 'user_geolocation' => $user_geolocation, 'prompt' => $prompt, 'ai_output' => $response_content, 'data' => $formData, 'used_token' => $used_token, 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ), array('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ); return $response_Id; } /** * The function to download the user stats based on the * duration set by the user */ function ai_app_onsite_download_user_stats() { global $wpdb; if ( ! isset($_POST['ai_app_onsite_download_user_stats_nonce']) || ! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['ai_app_onsite_download_user_stats_nonce'])), 'ai_app_onsite_download_user_stats_nonce') ) { wp_send_json_error(['message' => 'Nonce verification failed for this action.']); return; } $app_selector = ai_app_onsite_get_app_selector(); if (!$app_selector) { wp_send_json_error('App not found. Please create an app first.'); exit; } $duration = isset($_POST['duration']) ? sanitize_text_field(wp_unslash($_POST['duration'])) : ''; if (empty($duration)) { wp_send_json_error('Duration parameter is required.'); return; } $where_clauses = ["app_id = %d"]; $query_params = [$app_selector]; $today = date('Y-m-d', strtotime('-1 day')); $now = current_time('mysql'); $timezone = wp_timezone(); $now_dt = new DateTime('now', $timezone); $today_start_dt = new DateTime('today midnight', $timezone); switch ($duration) { case 'today': $where_clauses[] = "updated_at BETWEEN %s AND %s"; $query_params[] = $today_start_dt->format('Y-m-d H:i:s'); $query_params[] = $now_dt->format('Y-m-d H:i:s'); break; case 'last_7_days': $start = date('Y-m-d', strtotime('-7 days')); $where_clauses[] = "DATE(updated_at) BETWEEN %s AND %s"; $query_params[] = $start; $query_params[] = $today; break; case 'current_month': $start = date('Y-m-01'); $where_clauses[] = "DATE(updated_at) BETWEEN %s AND %s"; $query_params[] = $start; $query_params[] = $today; break; case 'last_month': $start = date('Y-m-01', strtotime('first day of last month')); $end = date('Y-m-t', strtotime('last day of last month')); $where_clauses[] = "DATE(updated_at) BETWEEN %s AND %s"; $query_params[] = $start; $query_params[] = $end; break; default: break; } $table_name = $wpdb->prefix . 'ai_app_onsite_app_usage_statistics'; $sql = "SELECT * FROM $table_name WHERE " . implode(" AND ", $where_clauses); $prepared_sql = $wpdb->prepare($sql, $query_params); $user_stats = $wpdb->get_results($prepared_sql, ARRAY_A); if (!$user_stats) { wp_send_json_error('No user statistics found for this duration.'); return; } foreach ($user_stats as &$row) { if (!empty($row['response_id'])) { $response_data = $wpdb->get_row( $wpdb->prepare( "SELECT data FROM " . $wpdb->prefix . "ai_app_onsite_user_statistics WHERE response_id = %s LIMIT 1", $row['response_id'] ), ARRAY_A ); if ($response_data && !empty($response_data['data'])) { $data_array = json_decode($response_data['data'], true); if (json_last_error() === JSON_ERROR_NONE && isset($data_array['email']) && !empty($data_array['email'])) { $row['user_id'] = sanitize_email($data_array['email']); } } } } unset($row); // Begin CSV output if (ob_get_contents()) ob_end_clean(); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="user_stats_export.csv"'); header('Pragma: no-cache'); header('Expires: 0'); $output = fopen('php://output', 'w'); // ======================== // Summary (Top 4 Rows) // ======================== // Row 1 $monthTitle = ucfirst(str_replace('_', ' ', $duration)); $app_name = ai_app_onsite_get_current_app_name(); fputcsv($output, ["AiappOnsite User Stats: $monthTitle - $app_name"]); // Row 2: Empty fputcsv($output, []); // Row 3: Summary headers fputcsv($output, [ 'App Visitor Total', 'Time on App (Avg)', 'App Use Total', 'Results Visitors Total', 'Time on Results (Avg)', 'App Reuse Total', 'Results Liked Total', 'Results Disliked Total', 'Report Date/Time' ]); // Row 4: Summary values $summary = [ 'visitor_total' => count(array_unique(array_column($user_stats, 'user_id'))), 'time_on_app_avg' => $this->average_duration(array_column($user_stats, 'time_on_app')), 'app_use_total' => count(array_filter($user_stats, fn($row) => $row['app_use'] === 'Y')), 'results_visited' => count(array_filter($user_stats, fn($row) => $row['results_visited'] === 'Y')), 'time_on_result_avg'=> $this->average_duration(array_column($user_stats, 'time_on_results')), 'app_reuse' => count(array_filter($user_stats, fn($row) => $row['app_reuse'] === 'Y')), 'liked' => count(array_filter($user_stats, fn($row) => $row['results_liked'] === 'Y')), 'disliked' => count(array_filter($user_stats, fn($row) => $row['results_disliked'] === 'Y')), 'report_time' => current_time('mysql') ]; fputcsv($output, array_values($summary)); // Row 5: Empty line fputcsv($output, []); // ======================== // Detail Rows // ======================== $headers = [ 'App Visitor (User ID)', 'Time on App', 'App Use', 'Results Visited', 'Time on Results', 'App Reuse', 'Results Liked', 'Results Disliked', 'Date/Time' ]; fputcsv($output, $headers); foreach ($user_stats as $row) { fputcsv($output, [ $row['user_id'], $row['time_on_app'], $row['app_use'], $row['results_visited'], $row['time_on_results'], $row['app_reuse'], $row['results_liked'], $row['results_disliked'], $row['updated_at'] ]); } fclose($output); exit; } // Helper to calculate average time (hh:mm:ss format) public function average_duration($timeArray): string { $totalSeconds = 0; $count = 0; foreach ($timeArray as $timeStr) { if (!$timeStr) continue; $parts = explode(':', $timeStr); // Strictly mm:ss format if (count($parts) === 2) { [$m, $s] = $parts; if (is_numeric($m) && is_numeric($s)) { $totalSeconds += ((int)$m * 60) + (int)$s; $count++; } } } if ($count === 0) return '00:00:00'; $avgSeconds = (int)($totalSeconds / $count); return gmdate('H:i:s', $avgSeconds); // Format as HH:MM:SS } function ai_app_onsite_save_feedback() { global $wpdb; if (!isset($_POST['response_id']) || !isset($_POST['feedback']) || empty($_POST['response_id']) || empty($_POST['feedback'])) { wp_send_json_error('Invalid Data'); } $response_id = sanitize_text_field($_POST['response_id']); $feedback = sanitize_text_field($_POST['feedback']); $table_name_stats = $wpdb->prefix . 'ai_app_onsite_user_statistics'; $update = $wpdb->update( $table_name_stats, ['feedback' => $feedback, 'updated_at' => current_time('mysql')], ['response_id' => $response_id], ['%s', '%s'], ['%s'] ); if ($update === false) { wp_send_json_error('Failed to save feedback'); } wp_send_json_success('Feedback saved successfully'); } /** * The function to store the user usage session based on the * app interaction */ function ai_app_onsite_store_session() { if (empty($_POST['app_id']) || empty($_POST['user_id'])) { wp_send_json_error('Missing required session fields'); } global $wpdb; $table_name = $wpdb->prefix . 'ai_app_onsite_app_usage_statistics'; // Sanitize inputs $session_data = array( 'app_id' => sanitize_text_field($_POST['app_id']), 'response_id' => sanitize_text_field($_POST['response_id'] ?? ''), 'user_id' => sanitize_text_field($_POST['user_id']), 'time_on_app' => sanitize_text_field($_POST['time_on_app'] ?? ''), 'app_use' => sanitize_text_field($_POST['app_use'] ?? 'N'), 'results_visited' => sanitize_text_field($_POST['results_visited'] ?? 'N'), 'time_on_results' => sanitize_text_field($_POST['time_on_results'] ?? ''), 'app_reuse' => sanitize_text_field($_POST['app_reuse'] ?? 'N'), 'results_liked' => sanitize_text_field($_POST['results_liked'] ?: 'N'), 'results_disliked'=> sanitize_text_field($_POST['results_disliked'] ?: 'N'), 'updated_at' => current_time('mysql') ); // Check if the session already exists $existing_session = $wpdb->get_row( $wpdb->prepare( "SELECT id, time_on_app, time_on_results FROM $table_name WHERE app_id = %s AND user_id = %s ORDER BY updated_at DESC, id DESC LIMIT 1", $session_data['app_id'], $session_data['user_id'] ) ); if ($existing_session) { unset($session_data['created_at']); // Update existing session $session_data['time_on_app'] = $this->aiao_sum_time($existing_session->time_on_app, $session_data['time_on_app']); $session_data['time_on_results'] = $this->aiao_sum_time($existing_session->time_on_results, $session_data['time_on_results']); $wpdb->update( $table_name, $session_data, ['id' => $existing_session->id], array_fill(0, count($session_data), '%s'), ['%d'] ); if($session_data['app_reuse'] === 'Y') { $wpdb->insert( $table_name, [ 'app_id' => $session_data['app_id'], 'user_id' => $session_data['user_id'], 'created_at' => current_time('mysql'), 'updated_at' => current_time('mysql') ], array_fill(0, 4, '%s')); } } else { $session_data['created_at'] = current_time('mysql'); // Insert new session $wpdb->insert( $table_name, $session_data, array_fill(0, count($session_data), '%s') ); } // Check for errors if ($wpdb->last_error) { wp_send_json_error('Error storing session data: ' . $wpdb->last_error); } } // Helper to sum two 'i:s' time strings function aiao_sum_time($time1, $time2) { if (empty($time1)) { return $time2; } if (empty($time2)) { return $time1; } $parts1 = explode(':', str_pad($time1, 5, '0', STR_PAD_LEFT)); $parts2 = explode(':', str_pad($time2, 5, '0', STR_PAD_LEFT)); $sec = ((int)$parts1[0] * 60 + (int)$parts1[1]) + ((int)$parts2[0] * 60 + (int)$parts2[1]); return sprintf('%02d:%02d', floor($sec / 60), $sec % 60); } /** * Generate a CSVs for today's usage * */ function ai_app_onsite_user_stats_callback() { global $wpdb; $table = $wpdb->prefix . 'ai_app_onsite_app_usage_statistics'; $rows = $wpdb->get_results("SELECT * FROM $table ORDER BY app_id, user_id", ARRAY_A); if (empty($rows)) { wp_send_json_error('No user statistics found for today.'); return; } // --- credentials from Step 1 registration --- $client_credentials = get_option('aiapp_client_credentials', []); $key = $client_credentials['key'] ?? ''; $secret = $client_credentials['secret'] ?? ''; if ($key === '' || $secret === '') { wp_send_json_error('Missing client credentials. Registration not completed.'); return; } $api_base = rtrim(get_option('aiapp_server_base_url'), '/'); if ($api_base === '') { wp_send_json_error('AIAO API base URL not configured.'); return; } $site_unique_id = get_option('ai_app_onsite_unique_id') ?: 'Unknown'; $now_stamp = current_time('mysql'); $filename_stamp = date('Y-m-d-H-i-s'); $csv_filename = "user_stats_{$site_unique_id}_{$filename_stamp}.csv"; // Write CSV to memory (not php://output!) $csv_stream = fopen('php://temp', 'r+'); // memory-safe temp stream fwrite($csv_stream, "\xEF\xBB\xBF"); // UTF-8 BOM $grouped = []; foreach ($rows as $row) { $grouped[$row['app_id']][] = $row; } foreach ($grouped as $app_id => $app_rows) { fputcsv($csv_stream, ["AIappOnsite User Stats - Site: $site_unique_id"]); fputcsv($csv_stream, []); fputcsv($csv_stream, [ 'App Visitor Total', 'Time on App (Avg)', 'App Use Total', 'Results Visitors Total', 'Time on Results (Avg)', 'App Reuse Total', 'Results Liked Total', 'Results Disliked Total', 'Report Date/Time' ]); $visitor_total = count(array_unique(array_column($app_rows, 'user_id'))); $time_on_app_avg = $this->average_duration(array_column($app_rows, 'time_on_app')); $app_use_total = count(array_filter($app_rows, fn($r) => strtoupper(trim($r['app_use'] ?? '')) === 'Y')); $results_visited = count(array_filter($app_rows, fn($r) => strtoupper(trim($r['results_visited'] ?? '')) === 'Y')); $time_on_result_avg = $this->average_duration(array_column($app_rows, 'time_on_results')); $app_reuse = count(array_filter($app_rows, fn($r) => strtoupper(trim($r['app_reuse'] ?? '')) === 'Y')); $liked = count(array_filter($app_rows, fn($r) => strtoupper(trim($r['results_liked'] ?? '')) === 'Y')); $disliked = count(array_filter($app_rows, fn($r) => strtoupper(trim($r['results_disliked'] ?? '')) === 'Y')); fputcsv($csv_stream, [ $visitor_total, $time_on_app_avg, $app_use_total, $results_visited, $time_on_result_avg, $app_reuse, $liked, $disliked, $now_stamp ]); fputcsv($csv_stream, []); } rewind($csv_stream); $csv_data = stream_get_contents($csv_stream); fclose($csv_stream); $ts = (string) time(); $checksum = hash('sha256', $csv_data); $sig = base64_encode(hash_hmac('sha256', $ts . ':' . $checksum, $secret, true)); // Upload to remote server $upload_url = $api_base . '/wp-json/aiao/v1/csv-upload'; $args = [ 'method' => 'POST', 'timeout' => 60, 'headers' => [ 'X-Filename' => $csv_filename, 'Content-Type' => 'text/csv', 'X-AIAO-Key' => $key, 'X-AIAO-Timestamp' => $ts, 'X-AIAO-Checksum' => $checksum, 'X-AIAO-Signature' => $sig, 'X-AIAO-Filename' => $csv_filename, 'Expect' => '', ], 'body' => $csv_data, 'sslverify' => true, ]; $response = wp_remote_post($upload_url, $args); if (is_wp_error($response)) { error_log('CSV Upload Failed: ' . $response->get_error_message()); wp_send_json_error('CSV upload failed'); } else { error_log('CSV uploaded successfully: ' . $csv_filename); if (!wp_next_scheduled('ai_app_onsite_user_stats_cron')) { $random_time = $this->ai_app_onsite_generate_random_time(); wp_schedule_single_event($random_time, 'ai_app_onsite_user_stats_cron'); } wp_send_json_success('CSV uploaded and sent successfully'); } } } new AI_APP_ONSITE_User_Stats_Handler(); // Instantiate the class }