From c86019f2140ab9a6fae631208d6859f4a8a7e60f Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Thu, 16 Jul 2026 11:38:19 +0530 Subject: [PATCH] FIX_TPA_CLAM_FILE_DOWNLOAD --- app/Config/Acl.php | 3 + app/Config/Routes.php | 3 + app/Controllers/StorageFilesController.php | 93 +++++++++++++++++++++- app/Helpers/utility_helper.php | 38 +++++---- 4 files changed, 120 insertions(+), 17 deletions(-) diff --git a/app/Config/Acl.php b/app/Config/Acl.php index c471f1bb..712d7cdf 100644 --- a/app/Config/Acl.php +++ b/app/Config/Acl.php @@ -22,6 +22,9 @@ class Acl '#^/getSSORedirectUrl#' => ['public' => true], '#^/getEmployeePolicy#' => ['public' => true], '#^/downloadFileTableFile#' => ['public' => true], + '#^/storage/file-download#' => ['public' => true], + '#^/fileDownload#' => ['public' => true], + '#^/downloadClaimFile#' => ['public' => true], '#^/swagger#' => ['roles' => [ADMIN_ROLE_ID]], '#^/getEmployeeActiveOrInactivePolicy#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], '#^/test/chartbrewDashboardDemo#' => ['roles' => [ADMIN_ROLE_ID, HEAD_ROLE_ID]], diff --git a/app/Config/Routes.php b/app/Config/Routes.php index beeddf81..a8be516c 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -38,6 +38,9 @@ $routes->group('/storage/files', ['filter' => 'authMVC'], function ($routes) { $routes->get('list', 'StorageFilesController::listFiles'); $routes->get('folders', 'StorageFilesController::folders'); }); +// Force download (attachment) — public so TPA can fetch claim docs +$routes->get('storage/file-download/claim/(:segment)', 'StorageFilesController::forceDownloadClaim/$1'); +$routes->get('storage/file-download', 'StorageFilesController::forceDownloadByFolderFile'); $routes->get('/visitOffBoardCheck', 'EmployeeController::visitOffBoardCheck'); $routes->get('/metaDashboardDemo', 'TestingController::metaDashboardDemo'); $routes->get('/apacheSuperSetDemo', 'TestingController::apacheSuperSetDemo'); diff --git a/app/Controllers/StorageFilesController.php b/app/Controllers/StorageFilesController.php index 9fc0c0b8..31ef6aa3 100644 --- a/app/Controllers/StorageFilesController.php +++ b/app/Controllers/StorageFilesController.php @@ -2,8 +2,11 @@ namespace App\Controllers; +use App\Models\ClaimFilesModel; +use CodeIgniter\Exceptions\PageNotFoundException; + /** - * Browse S3 upload-bucket objects folder-wise with paginated search UI. + * Browse S3 upload-bucket objects + file-download (attachment) APIs. */ class StorageFilesController extends AdminController { @@ -158,6 +161,94 @@ class StorageFilesController extends AdminController ]); } + /** + * GET /storage/file-download/claim/{md5(id)} + * Streams claim_files row by MD5(id) as attachment (never inline). + */ + public function forceDownloadClaim($md5Id = null) + { + helper('utility'); + + $md5Id = strtolower(trim((string) $md5Id)); + if ($md5Id === '' || ! preg_match('/^[a-f0-9]{32}$/', $md5Id)) { + throw PageNotFoundException::forPageNotFound(); + } + + $claimFiles = new ClaimFilesModel(); + $record = $claimFiles + ->where('MD5(CAST(id AS CHAR))', $md5Id) + ->where('is_active', 1) + ->first(); + if (empty($record)) { + throw PageNotFoundException::forPageNotFound(); + } + + $diskName = ! empty($record['url']) + ? basename((string) $record['url']) + : basename((string) ($record['file_name'] ?? '')); + + if ($diskName === '') { + throw PageNotFoundException::forPageNotFound(); + } + + $downloadAs = storage_upload_display_name($diskName); + + return $this->streamAttachment(WRITEPATH . 'uploads/claim_files', $diskName, $downloadAs); + } + + /** + * GET /storage/file-download?folder=claim_files&file=xxx.pdf + * Streams any storage module object as attachment (never inline). + */ + public function forceDownloadByFolderFile() + { + helper('utility'); + + $folder = basename(str_replace('\\', '/', trim((string) $this->request->getGet('folder')))); + $file = basename(str_replace('\\', '/', trim((string) $this->request->getGet('file')))); + + if ( + $folder === '' || $folder === '.' || $folder === '..' + || $file === '' || $file === '.' || $file === '..' + || preg_match('/[^a-zA-Z0-9_\-]/', $folder) + || preg_match('/[\\\\\\/]/', $file) + ) { + throw PageNotFoundException::forPageNotFound(); + } + + $uploadPath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . $folder; + $downloadAs = storage_upload_display_name($file); + + return $this->streamAttachment($uploadPath, $file, $downloadAs); + } + + /** + * Stream file bytes with attachment disposition (never inline / never S3 redirect). + */ + private function streamAttachment(string $uploadPath, string $diskName, string $downloadAs) + { + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadPath, $diskName); + + if (! ($result['success'] ?? false)) { + throw PageNotFoundException::forPageNotFound(); + } + + if (! empty($result['path']) && is_file($result['path'])) { + return $this->response + ->download($result['path'], null) + ->setFileName($downloadAs); + } + + if (! empty($result['content'])) { + return $this->response + ->download($downloadAs, $result['content']) + ->setFileName($downloadAs); + } + + throw PageNotFoundException::forPageNotFound(); + } + /** @return list */ private function localUploadFolders(): array { diff --git a/app/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 8c35373a..233f91d2 100755 --- a/app/Helpers/utility_helper.php +++ b/app/Helpers/utility_helper.php @@ -579,7 +579,8 @@ if (! function_exists('storage_claim_file_Upload')) { /** * Build a TPA-facing download URL for a claim file. - * Prefers an S3 presigned URL; falls back to app downloadClaimFile/{id} for local mode. + * Prefers Nhance file-download API (attachment stream from local/S3). + * Falls back to folder+file URL when file id is missing. */ if (! function_exists('storage_claim_file_download_url')) { function storage_claim_file_download_url(string $diskName, $fileId = null, int $expirationMinutes = 360): string @@ -592,22 +593,31 @@ if (! function_exists('storage_claim_file_download_url')) { $uploadPath = WRITEPATH . 'uploads/claim_files'; $storage = \Config\Services::getFileStorageService(); + // Prefer app file-download URL when claim_files id is known (TPA-safe, attachment). + if ($fileId !== null && $fileId !== '') { + if ($storage->usesS3()) { + if (! $storage->exists($uploadPath, $diskName)) { + log_message('error', 'storage_claim_file_download_url | missing on S3 | ' . $diskName); + return ''; + } + } else { + $localPath = $storage->resolveLocalPath($uploadPath, $diskName); + if (! is_file($localPath)) { + return ''; + } + } + + return base_url('storage/file-download/claim/' . md5((string) (int) $fileId)); + } + if ($storage->usesS3()) { if (! $storage->exists($uploadPath, $diskName)) { log_message('error', 'storage_claim_file_download_url | missing on S3 | ' . $diskName); return ''; } - $result = $storage->getPresignedUrl($uploadPath, $diskName, max(1, $expirationMinutes)); - if (($result['success'] ?? false) && ! empty($result['url'])) { - return (string) $result['url']; - } - - log_message('error', 'storage_claim_file_download_url | presign failed | ' . json_encode([ - 'disk_name' => $diskName, - 'message' => $result['message'] ?? null, - ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); - return ''; + // No file id: folder+file download endpoint + return base_url('storage/file-download') . '?folder=claim_files&file=' . rawurlencode($diskName); } $localPath = $storage->resolveLocalPath($uploadPath, $diskName); @@ -615,11 +625,7 @@ if (! function_exists('storage_claim_file_download_url')) { return ''; } - if ($fileId !== null && $fileId !== '') { - return base_url('downloadClaimFile/' . $fileId); - } - - return ''; + return base_url('storage/file-download') . '?folder=claim_files&file=' . rawurlencode($diskName); } }