isMethod('GET')) { return; } header('Allow: GET'); http_response_code(405); exit; } } if (!function_exists('exportResolveFlavor')) { /** * Reads `format=csv|excel` from the request. Unknown values fall * back to 'csv'. Accepts legacy alias 'excel-csv'. */ function exportResolveFlavor(RequestInput $request, string $default = CsvExportService::FLAVOR_CSV): string { $raw = strtolower(trim((string) $request->query('format', $default))); if ($raw === 'excel' || $raw === 'excel-csv' || $raw === 'xls') { return CsvExportService::FLAVOR_EXCEL; } return CsvExportService::FLAVOR_CSV; } } if (!function_exists('exportCapLimit')) { /** * Bounds a user-supplied limit to [1, $max]. Non-numeric / <=0 * values collapse to $max. */ function exportCapLimit(mixed $requested, int $max = 5000): int { $value = is_numeric($requested) ? (int) $requested : 0; if ($value < 1 || $value > $max) { return $max; } return $value; } } if (!function_exists('exportSendCsv')) { /** * Streams rows as a CSV download and ends the response. * * IMPORTANT: this helper does NOT enforce authentication or authorization. * Callers MUST run `Guard::requireLogin()` and the appropriate * `Guard::requireAbilityOrForbidden(...)` check BEFORE invoking it — * see `modules/helpdesk/pages/helpdesk/domains/export().php` for the * reference pattern. * * @param iterable> $rows * @param list $columns */ function exportSendCsv( string $filename, iterable $rows, array $columns, string $flavor = CsvExportService::FLAVOR_CSV ): void { $safeName = preg_replace('/[^A-Za-z0-9._-]+/', '-', $filename) ?: 'export.csv'; // Clamp total filename to 100 chars while preserving the .csv suffix. if (strlen($safeName) > 100) { $safeName = substr($safeName, 0, 96); } if (!str_ends_with(strtolower($safeName), '.csv')) { $safeName .= '.csv'; } while (ob_get_level() > 0) { ob_end_clean(); } header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $safeName . '"'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); header('X-Content-Type-Options: nosniff'); $handle = fopen('php://output', 'w'); if ($handle === false) { exit; } $service = new CsvExportService(); $service->writeTo($handle, $rows, $columns, $flavor); fclose($handle); exit; } }