'Your plan does not include exports'], 403); $canVoiceExport = (bool)featureValue($user, 'voice_export', false); $format = strtolower((string)($_GET['format'] ?? 'json')); $export = array_map(function(array $row) use ($canVoiceExport): array { $item = [ 'id' => (int)$row['id'], 'username' => $row['username'], 'message' => $row['message'], 'type' => $row['message_type'], 'created_at' => date(DATE_ATOM, (int)$row['created_at']), ]; if ($row['message_type'] === 'voice' && $canVoiceExport && !empty($row['voice_file'])) { $item['voice_url'] = appBaseUrl() . '/' . voicePublicPath($row['voice_file']); } return $item; }, $rows); $filename = 'cyberchat-archive-' . date('Y-m-d'); if ($format === 'csv') { header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '.csv"'); $out = fopen('php://output', 'w'); fputcsv($out, ['id', 'username', 'message', 'type', 'created_at', 'voice_url']); foreach ($export as $row) fputcsv($out, [ $row['id'], $row['username'], $row['message'], $row['type'], $row['created_at'], $row['voice_url'] ?? '', ]); fclose($out); exit; } header('Content-Type: application/json; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '.json"'); echo json_encode(['exported_at' => date(DATE_ATOM), 'messages' => $export], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); exit; } $payload = array_map(function(array $row): array { return [ 'id' => (int)$row['id'], 'message' => $row['message'], 'message_type' => $row['message_type'], 'voice_url' => $row['message_type'] === 'voice' && !empty($row['voice_file']) ? voicePublicPath($row['voice_file']) : null, 'voice_mime' => $row['voice_mime'] ?? null, 'voice_duration' => $row['voice_duration'] !== null ? (float)$row['voice_duration'] : null, 'created_at' => (int)$row['created_at'], 'day_key' => $row['day_key'], ]; }, $rows); jsonResponse([ 'messages' => $payload, 'retention_days' => max(30, (int)featureValue($user, 'text_archive_days', 30)), 'can_export' => (bool)featureValue($user, 'text_export', false), 'voice_included' => (bool)featureValue($user, 'voice_archive', false), ]);