From 62b5ddbeaffe5089fdc6d8f756dfaf6973a3cbaa Mon Sep 17 00:00:00 2001 From: Venkatesh Date: Wed, 15 Jul 2026 15:49:13 +0530 Subject: [PATCH] FEAT_S3_FILE_UPLOAD --- .env.sample | 8 + .gitignore | 4 + app/Commands/MigrateLocalFilesToS3.php | 217 ++++++ app/Commands/StorageMergePdfSmokeTest.php | 361 ++++++++++ app/Commands/StoragePurgeS3.php | 189 +++++ app/Commands/StorageSmokeTest.php | 514 ++++++++++++++ app/Config/Routes.php | 3 + app/Config/Services.php | 10 + .../Api/NonEbClaimApiController.php | 21 +- app/Controllers/ClaimsUploadController.php | 63 +- app/Controllers/ClientController.php | 317 ++++++--- app/Controllers/EmpDataServiceController.php | 28 +- app/Controllers/EmployeeController.php | 127 ++-- .../EmployeeMultiEventServiceController.php | 34 +- app/Controllers/EmployeeRestController.php | 111 ++- app/Controllers/EmployeeServiceController.php | 32 +- app/Controllers/FhplApiController.php | 19 +- app/Controllers/HealthIndiaApiController.php | 19 +- app/Controllers/LeadsController.php | 121 ++-- app/Controllers/MasterController.php | 2 + app/Controllers/MediAssistApiController.php | 30 +- app/Controllers/NonEbClaimController.php | 7 +- app/Controllers/NotificationController.php | 9 +- .../PolicyTransactionController.php | 142 ++-- .../RestAuthenticationController.php | 1 + app/Controllers/TicketController.php | 191 ++--- app/Controllers/TicketServiceController.php | 12 +- app/Controllers/VidalApiController.php | 30 +- app/Controllers/VoloApiController.php | 10 +- app/Helpers/merge_pdf_helper.php | 69 +- app/Helpers/sendMailNotification.php | 55 +- app/Helpers/utility_helper.php | 665 +++++++++++++++++- app/Libraries/FileStorageService.php | 342 +++++++++ app/Libraries/S3Service.php | 166 +++-- app/Libraries/ZipService.php | 2 +- app/Models/ClientKYCDocsModel.php | 2 +- app/Views/claim_files_upload.php | 6 +- app/Views/client_kyc.php | 12 +- app/Views/client_kyc_2.php | 12 +- app/Views/client_kyc_other_table.php | 5 +- app/Views/client_kyc_primary_table.php | 12 +- app/Views/client_kyc_single_table.php | 10 +- .../policy_transaction_endorsement_list.php | 6 +- .../policy_transaction_inception_list.php | 11 +- .../policy_transaction_inception_list_2.php | 11 +- app/Views/ticket_conversation.php | 2 +- writable/cache/claim_files_runtime/.gitkeep | 0 writable/cache/storage_runtime/.gitkeep | 0 48 files changed, 3428 insertions(+), 592 deletions(-) create mode 100644 app/Commands/MigrateLocalFilesToS3.php create mode 100644 app/Commands/StorageMergePdfSmokeTest.php create mode 100644 app/Commands/StoragePurgeS3.php create mode 100644 app/Commands/StorageSmokeTest.php create mode 100644 app/Libraries/FileStorageService.php create mode 100644 writable/cache/claim_files_runtime/.gitkeep create mode 100644 writable/cache/storage_runtime/.gitkeep diff --git a/.env.sample b/.env.sample index 65018cd5..64b3db53 100755 --- a/.env.sample +++ b/.env.sample @@ -189,3 +189,11 @@ ICICI_GRANT_TYPE = ICICI_PRIMARY_KEY_CONSTANT = + +#-------------------------------------------------------------------- +# File storage (S3 uploads) +#-------------------------------------------------------------------- +# FILE_STORAGE_DRIVER = s3 +# AWS_FILE_UPLOAD_BUCKET = +# Keep permanent local copy after S3 upload in storage_file_Upload (default false) +RETAIN_LOCAL = false diff --git a/.gitignore b/.gitignore index 91df762f..f2a27c22 100755 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ #------------------------- writable/cache/* !writable/cache/.gitkeep +!writable/cache/storage_runtime/ +!writable/cache/storage_runtime/.gitkeep +!writable/cache/claim_files_runtime/ +!writable/cache/claim_files_runtime/.gitkeep writable/logs/* !writable/logs/.gitkeep diff --git a/app/Commands/MigrateLocalFilesToS3.php b/app/Commands/MigrateLocalFilesToS3.php new file mode 100644 index 00000000..d0fcb058 --- /dev/null +++ b/app/Commands/MigrateLocalFilesToS3.php @@ -0,0 +1,217 @@ +|insurer_claim_form|all] [--limit 500] [--offset 0] [--execute]'; + protected $options = [ + '--module' => 'Target module/folder (default: writable_uploads). Use writable_uploads for all writable/uploads/* folders.', + '--limit' => 'Max files per run (default: 500).', + '--offset' => 'Start offset for phased batches (default: 0).', + '--execute' => 'Actually upload files. Without this flag, command runs in dry-run mode.', + ]; + + /** + * @var array + */ + private array $staticPaths = [ + 'insurer_claim_form' => WRITEPATH . 'insurer_claim_form', + ]; + + public function run(array $params) + { + $moduleOpt = strtolower((string) (CLI::getOption('module') ?? 'writable_uploads')); + $execute = CLI::getOption('execute') !== null; + $dryRun = !$execute; + $limit = max(1, (int) (CLI::getOption('limit') ?? 500)); + $offset = max(0, (int) (CLI::getOption('offset') ?? 0)); + $modulePaths = $this->buildModulePaths(); + + $selectedModules = $this->resolveModules($moduleOpt, $modulePaths); + if ($selectedModules === []) { + CLI::error('Invalid --module value. Allowed: writable_uploads, ' . implode(', ', array_keys($modulePaths)) . ', all'); + return; + } + + $storage = \Config\Services::getFileStorageService(false); + if (!$storage->usesS3()) { + CLI::error('FileStorageService is not in S3 mode. Set FILE_STORAGE_DRIVER=s3 before migration.'); + return; + } + + CLI::write($dryRun ? 'Running in DRY-RUN mode (no uploads).' : 'Running in EXECUTE mode (uploads enabled).', 'yellow'); + CLI::write('Target bucket: ' . ($storage->getBucket() ?? 'N/A'), 'cyan'); + CLI::newLine(); + + $totals = [ + 'seen' => 0, + 'uploaded' => 0, + 'skipped_exists' => 0, + 'skipped_missing' => 0, + 'failed' => 0, + ]; + + foreach ($selectedModules as $module) { + $path = $modulePaths[$module]; + CLI::write("Module: {$module}", 'green'); + CLI::write("Path: {$path}"); + + if (!is_dir($path)) { + CLI::write(' - Skipped: folder does not exist', 'yellow'); + $totals['skipped_missing']++; + CLI::newLine(); + continue; + } + + $files = $this->listFiles($path); + $files = array_slice($files, $offset, $limit); + CLI::write(' - Files in batch: ' . count($files)); + + foreach ($files as $fullPath) { + $totals['seen']++; + $fileName = basename($fullPath); + + if (!is_file($fullPath)) { + $totals['skipped_missing']++; + CLI::write(" - Missing: {$fileName}", 'yellow'); + continue; + } + + $alreadyExists = $storage->exists($path, $fileName); + if ($alreadyExists) { + $totals['skipped_exists']++; + CLI::write(" - Exists in S3, skipped: {$fileName}", 'yellow'); + continue; + } + + if ($dryRun) { + CLI::write(" - [dry-run] Would upload: {$fileName}"); + continue; + } + + $result = $storage->upload($fullPath, $path, $fileName); + if ($result['success'] ?? false) { + $totals['uploaded']++; + CLI::write(" - Uploaded: {$fileName}", 'green'); + } else { + $totals['failed']++; + CLI::write(" - Failed: {$fileName} | " . ($result['message'] ?? 'unknown error'), 'red'); + } + } + + CLI::newLine(); + } + + CLI::write('Migration summary', 'cyan'); + CLI::write(' Seen: ' . $totals['seen']); + CLI::write(' Uploaded: ' . $totals['uploaded'], $totals['uploaded'] > 0 ? 'green' : 'white'); + CLI::write(' Skipped (already in S3): ' . $totals['skipped_exists'], 'yellow'); + CLI::write(' Skipped (missing/invalid): ' . $totals['skipped_missing'], 'yellow'); + CLI::write(' Failed: ' . $totals['failed'], $totals['failed'] > 0 ? 'red' : 'white'); + + if ($dryRun) { + CLI::newLine(); + CLI::write('Dry-run complete. Re-run with --execute to perform uploads.', 'yellow'); + } + } + + /** + * @return array + */ + private function resolveModules(string $moduleOpt, array $modulePaths): array + { + if ($moduleOpt === 'all') { + return array_keys($modulePaths); + } + + if ($moduleOpt === 'writable_uploads') { + $uploadRoot = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR; + $modules = []; + foreach ($modulePaths as $module => $path) { + $normalized = str_replace('\\', '/', $path); + $normalizedRoot = str_replace('\\', '/', $uploadRoot); + if (strpos($normalized, rtrim($normalizedRoot, '/')) === 0) { + $modules[] = $module; + } + } + + return $modules; + } + + $raw = array_filter(array_map('trim', explode(',', $moduleOpt))); + $valid = []; + foreach ($raw as $module) { + if (array_key_exists($module, $modulePaths)) { + $valid[] = $module; + } + } + + return array_values(array_unique($valid)); + } + + /** + * Build migratable paths: + * - all direct subfolders in writable/uploads + * - additional static paths (outside writable/uploads) + * + * @return array + */ + private function buildModulePaths(): array + { + $paths = []; + $uploadRoot = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads'; + $entries = @scandir($uploadRoot); + + if (is_array($entries)) { + foreach ($entries as $entry) { + if ($entry === '.' || $entry === '..') { + continue; + } + + $fullPath = $uploadRoot . DIRECTORY_SEPARATOR . $entry; + if (is_dir($fullPath)) { + $paths[strtolower($entry)] = $fullPath; + } + } + } + + foreach ($this->staticPaths as $key => $path) { + $paths[$key] = $path; + } + + ksort($paths); + return $paths; + } + + /** + * @return array + */ + private function listFiles(string $path): array + { + $entries = @scandir($path); + if (!is_array($entries)) { + return []; + } + + $files = []; + foreach ($entries as $entry) { + if ($entry === '.' || $entry === '..') { + continue; + } + $fullPath = rtrim($path, '/\\') . DIRECTORY_SEPARATOR . $entry; + if (is_file($fullPath)) { + $files[] = $fullPath; + } + } + + sort($files); + return $files; + } +} diff --git a/app/Commands/StorageMergePdfSmokeTest.php b/app/Commands/StorageMergePdfSmokeTest.php new file mode 100644 index 00000000..715bec26 --- /dev/null +++ b/app/Commands/StorageMergePdfSmokeTest.php @@ -0,0 +1,361 @@ + 'List merge scenarios without executing.', + '--keep' => 'Skip cleanup of S3 objects and claim_files rows.', + ]; + + private int $pass = 0; + private int $fail = 0; + private int $skip = 0; + + /** @var list */ + private array $catalog = [ + ['id' => 'MERGE-01', 'title' => 'Create & upload 2 source PDFs to S3 (claim_files, no permanent local)'], + ['id' => 'MERGE-02', 'title' => 'Insert claim_files DB rows for synthetic ticket'], + ['id' => 'MERGE-03', 'title' => 'merge_ticket_pdfs resolves sources from S3 (temp download)'], + ['id' => 'MERGE-04', 'title' => 'Merge succeeds with pages >= 2'], + ['id' => 'MERGE-05', 'title' => 'Merged PDF exists on S3'], + ['id' => 'MERGE-06', 'title' => 'Merged permanent local path gone when S3 enabled'], + ['id' => 'MERGE-07', 'title' => 'Merged claim_files row registered (file_type=4)'], + ['id' => 'MERGE-08', 'title' => 'Claim runtime temps cleaned after merge'], + ['id' => 'MERGE-09', 'title' => 'Cleanup removes S3 objects + soft-deletes DB rows'], + ]; + + public function run(array $params) + { + helper(['utility', 'merge_pdf']); + + if (CLI::getOption('list') !== null) { + CLI::write('========== MERGE PDF SCENARIOS ==========', 'cyan'); + foreach ($this->catalog as $row) { + CLI::write(" {$row['id']} {$row['title']}"); + } + CLI::newLine(); + CLI::write('Run: php spark storage:smoke-test-merge-pdf'); + return EXIT_SUCCESS; + } + + $storage = \Config\Services::getFileStorageService(false); + $keep = CLI::getOption('keep') !== null; + + CLI::write('========== CLAIM PDF MERGE SMOKE ==========', 'cyan'); + CLI::write('usesS3: ' . ($storage->usesS3() ? 'YES' : 'NO')); + CLI::write('Bucket: ' . ($storage->getBucket() ?? 'N/A')); + CLI::write('RETAIN_LOCAL: ' . (storage_env_retain_local() ? 'true' : 'false')); + CLI::newLine(); + + if (! $storage->usesS3()) { + foreach ($this->catalog as $row) { + $this->skip($row['id'], 'FILE_STORAGE_DRIVER must be s3'); + } + $this->printResult(); + return EXIT_ERROR; + } + + $uploadDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR + . 'uploads' . DIRECTORY_SEPARATOR + . 'claim_files' . DIRECTORY_SEPARATOR; + if (! is_dir($uploadDir)) { + @mkdir($uploadDir, 0775, true); + } + + $ticketId = 990000000 + random_int(1000, 999999); + $stamp = date('Ymd_His') . '_' . bin2hex(random_bytes(3)); + $srcNames = [ + 'smoke_merge_a_' . $stamp . '.pdf', + 'smoke_merge_b_' . $stamp . '.pdf', + ]; + $sourceIds = []; + $mergedName = null; + $mergedId = null; + $claimFiles = new ClaimFilesModel(); + + try { + // MERGE-01 + $uploaded = 0; + foreach ($srcNames as $i => $name) { + $local = $this->makeOnePagePdf($uploadDir, $name, 'Smoke source ' . ($i + 1)); + if ($local === null || ! is_file($local)) { + continue; + } + $up = $storage->upload($local, rtrim($uploadDir, '/\\'), $name); + @unlink($local); + if (($up['success'] ?? false) && $storage->exists(rtrim($uploadDir, '/\\'), $name) && ! is_file($uploadDir . $name)) { + $uploaded++; + } + } + $this->step('MERGE-01', $uploaded === 2, "uploaded={$uploaded}/2 ticket_id={$ticketId}"); + + if ($uploaded !== 2) { + foreach (array_slice($this->catalog, 1) as $row) { + $this->skip($row['id'], 'source upload failed'); + } + $this->printResult(); + return EXIT_ERROR; + } + + // MERGE-02 + foreach ($srcNames as $idx => $name) { + $id = $claimFiles->insert([ + 'ticket_id' => $ticketId, + 'file_type' => 2, + 'doc_name' => 'SMOKE_MERGE_SRC_' . ($idx + 1), + 'file_name' => $name, + 'url' => $name, + 'mime_type' => 'application/pdf', + 'is_active' => 1, + 'created_by' => 1, + ]); + if ($id) { + $sourceIds[] = (int) $id; + } + } + $this->step('MERGE-02', count($sourceIds) === 2, 'ids=' . implode(',', $sourceIds)); + + if (count($sourceIds) !== 2) { + foreach (array_slice($this->catalog, 2) as $row) { + $this->skip($row['id'], 'DB insert failed'); + } + $this->cleanup($storage, $uploadDir, $srcNames, null, $ticketId, $sourceIds, $keep); + $this->printResult(); + return EXIT_ERROR; + } + + // MERGE-03..07 via merge_ticket_pdfs + $result = merge_ticket_pdfs($ticketId, [ + 'replace' => true, + 'created_by' => 1, + 'include_file_types' => [1, 2], + 'include_mime_types' => ['application/pdf', 'image/jpeg', 'image/png'], + ]); + + $this->step( + 'MERGE-03', + (bool) ($result['status'] ?? false) && (int) ($result['source_count'] ?? 0) >= 2, + (string) ($result['message'] ?? '') . ' sources=' . (int) ($result['source_count'] ?? 0) + ); + + $pages = (int) ($result['pages'] ?? 0); + $this->step( + 'MERGE-04', + (bool) ($result['status'] ?? false) && $pages >= 2, + 'pages=' . $pages + ); + + $mergedName = (string) ($result['file_name'] ?? ''); + $mergedId = $result['merged_file_id'] ?? null; + $onS3 = $mergedName !== '' && $storage->exists(rtrim($uploadDir, '/\\'), $mergedName); + $this->step('MERGE-05', $onS3, $mergedName !== '' ? $mergedName : 'no merged name'); + + $localMergedGone = $mergedName === '' || ! is_file($uploadDir . $mergedName); + $this->step('MERGE-06', $localMergedGone, $mergedName !== '' ? basename($mergedName) : 'n/a'); + + $rowOk = false; + if ($mergedId) { + $row = $claimFiles->find((int) $mergedId); + $rowOk = is_array($row) + && (int) ($row['file_type'] ?? 0) === MERGED_CLAIM_FILE_TYPE + && (int) ($row['is_active'] ?? 0) === 1 + && (string) ($row['file_name'] ?? '') === $mergedName; + } + $this->step('MERGE-07', $rowOk, 'merged_file_id=' . ($mergedId ?? 'null')); + + // MERGE-08: no leftover smoke temps for our source names in claim_files_runtime + $afterRuntime = $this->listClaimRuntimeFiles(); + $leaked = []; + foreach ($afterRuntime as $f) { + foreach ($srcNames as $src) { + if (strpos($f, $src) !== false) { + $leaked[] = $f; + } + } + } + $this->step('MERGE-08', $leaked === [], $leaked === [] ? 'no leaks' : implode(',', $leaked)); + + // MERGE-09 cleanup + $cleanOk = $this->cleanup( + $storage, + $uploadDir, + $srcNames, + $mergedName !== '' ? $mergedName : null, + $ticketId, + $sourceIds, + $keep, + $mergedId ? [(int) $mergedId] : [] + ); + if ($keep) { + $this->skip('MERGE-09', '--keep set; left ticket_id=' . $ticketId); + } else { + $srcGone = true; + foreach ($srcNames as $name) { + if ($storage->exists(rtrim($uploadDir, '/\\'), $name)) { + $srcGone = false; + } + } + $mergedGone = $mergedName === '' || ! $storage->exists(rtrim($uploadDir, '/\\'), $mergedName); + $this->step('MERGE-09', $cleanOk && $srcGone && $mergedGone, 'ticket_id=' . $ticketId); + } + } catch (\Throwable $e) { + CLI::error('Unhandled: ' . $e->getMessage()); + $this->fail++; + $this->cleanup($storage, $uploadDir, $srcNames, $mergedName, $ticketId, $sourceIds, $keep, $mergedId ? [(int) $mergedId] : []); + } + + $this->printResult(); + return $this->fail > 0 ? EXIT_ERROR : EXIT_SUCCESS; + } + + private function makeOnePagePdf(string $uploadDir, string $fileName, string $text): ?string + { + $tempDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'mpdf'; + if (! is_dir($tempDir)) { + @mkdir($tempDir, 0775, true); + } + $path = $uploadDir . $fileName; + try { + $mpdf = new Mpdf(['tempDir' => $tempDir, 'mode' => 'utf-8']); + $mpdf->WriteHTML('

' . htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . '

storage merge smoke

'); + $mpdf->Output($path, Destination::FILE); + return is_file($path) ? $path : null; + } catch (\Throwable $e) { + CLI::error('PDF create failed: ' . $e->getMessage()); + return null; + } + } + + /** @return list */ + private function listClaimRuntimeFiles(): array + { + $dir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR + . 'cache' . DIRECTORY_SEPARATOR + . 'claim_files_runtime' . DIRECTORY_SEPARATOR; + if (! is_dir($dir)) { + return []; + } + $out = []; + foreach (scandir($dir) ?: [] as $f) { + if ($f === '.' || $f === '..') { + continue; + } + if (is_file($dir . $f)) { + $out[] = $f; + } + } + return $out; + } + + /** + * @param list $srcNames + * @param list $sourceIds + * @param list $extraIds + */ + private function cleanup( + $storage, + string $uploadDir, + array $srcNames, + ?string $mergedName, + int $ticketId, + array $sourceIds, + bool $keep, + array $extraIds = [] + ): bool { + if ($keep) { + return true; + } + + $ok = true; + $dir = rtrim($uploadDir, '/\\'); + foreach ($srcNames as $name) { + if ($storage->exists($dir, $name)) { + $del = $storage->delete($dir, $name); + if (! ($del['success'] ?? false)) { + $ok = false; + } + } + @unlink($uploadDir . $name); + } + if ($mergedName) { + if ($storage->exists($dir, $mergedName)) { + $del = $storage->delete($dir, $mergedName); + if (! ($del['success'] ?? false)) { + $ok = false; + } + } + @unlink($uploadDir . $mergedName); + } + + try { + $claimFiles = new ClaimFilesModel(); + $ids = array_values(array_unique(array_merge($sourceIds, $extraIds))); + if ($ids !== []) { + $claimFiles->whereIn('id', $ids)->set(['is_active' => 0])->update(); + } + $claimFiles->where('ticket_id', $ticketId)->set(['is_active' => 0])->update(); + } catch (\Throwable $e) { + CLI::write('DB cleanup warning: ' . $e->getMessage(), 'yellow'); + $ok = false; + } + + return $ok; + } + + private function step(string $id, bool $ok, string $detail = ''): void + { + $title = $this->titleFor($id); + if ($ok) { + $this->pass++; + CLI::write("PASS {$id} {$title}" . ($detail !== '' ? " — {$detail}" : ''), 'green'); + } else { + $this->fail++; + CLI::write("FAIL {$id} {$title}" . ($detail !== '' ? " — {$detail}" : ''), 'red'); + } + } + + private function skip(string $id, string $reason): void + { + $this->skip++; + CLI::write("SKIP {$id} {$this->titleFor($id)} — {$reason}", 'yellow'); + } + + private function titleFor(string $id): string + { + foreach ($this->catalog as $row) { + if ($row['id'] === $id) { + return $row['title']; + } + } + return $id; + } + + private function printResult(): void + { + CLI::newLine(); + CLI::write( + sprintf('MERGE RESULT: %d passed | %d failed | %d skipped', $this->pass, $this->fail, $this->skip), + $this->fail === 0 ? 'green' : 'red' + ); + } +} diff --git a/app/Commands/StoragePurgeS3.php b/app/Commands/StoragePurgeS3.php new file mode 100644 index 00000000..7d4eaac0 --- /dev/null +++ b/app/Commands/StoragePurgeS3.php @@ -0,0 +1,189 @@ +[,folder...] [--limit 0] [--offset 0] [--execute] [--list-modules]'; + protected $options = [ + '--module' => 'Required. Module folder name(s), comma-separated (e.g. claim_files).', + '--limit' => 'Max objects to delete per module (0 = all, default: 0).', + '--offset' => 'Skip first N keys per module (default: 0).', + '--execute' => 'Actually delete. Without this flag, dry-run only.', + '--list-modules' => 'List known local upload module folder names and exit.', + ]; + + public function run(array $params) + { + if (CLI::getOption('list-modules') !== null) { + $this->printKnownModules(); + return EXIT_SUCCESS; + } + + $moduleOpt = trim((string) (CLI::getOption('module') ?? ($params[0] ?? ''))); + if ($moduleOpt === '') { + CLI::error('Missing --module. Example: php spark storage:purge-s3 --module claim_files'); + CLI::write('Tip: php spark storage:purge-s3 --list-modules'); + return EXIT_ERROR; + } + + $execute = CLI::getOption('execute') !== null; + $dryRun = ! $execute; + $limit = max(0, (int) (CLI::getOption('limit') ?? 0)); + $offset = max(0, (int) (CLI::getOption('offset') ?? 0)); + + $modules = $this->parseModules($moduleOpt); + if ($modules === []) { + CLI::error('No valid module names after parsing. Use basename-only folder names (no path).'); + return EXIT_ERROR; + } + + $storage = \Config\Services::getFileStorageService(false); + if (! $storage->usesS3()) { + CLI::error('FileStorageService is not in S3 mode. Set FILE_STORAGE_DRIVER=s3 and AWS_FILE_UPLOAD_BUCKET.'); + return EXIT_ERROR; + } + + $s3 = \Config\Services::getS3Service(false); + $bucket = $storage->getBucket(); + + CLI::write($dryRun ? 'Running in DRY-RUN mode (no deletes).' : 'Running in EXECUTE mode (deletes enabled).', 'yellow'); + CLI::write('Bucket: ' . ($bucket ?? 'N/A'), 'cyan'); + CLI::write('Modules: ' . implode(', ', $modules)); + CLI::newLine(); + + $totals = [ + 'listed' => 0, + 'deleted' => 0, + 'failed' => 0, + ]; + + foreach ($modules as $module) { + $prefix = $module . '/'; + CLI::write("Module: {$module}", 'green'); + CLI::write(" Prefix: {$prefix}"); + + $listed = $s3->listFiles($prefix, $bucket); + if (! ($listed['success'] ?? false)) { + CLI::error(' List failed: ' . ($listed['message'] ?? 'unknown')); + $totals['failed']++; + CLI::newLine(); + continue; + } + + $keys = []; + foreach ($listed['files'] ?? [] as $file) { + $key = (string) ($file['key'] ?? ''); + if ($key === '' || substr($key, -1) === '/') { + continue; // skip empty / folder markers + } + $keys[] = $key; + } + sort($keys); + + $batch = array_slice($keys, $offset, $limit > 0 ? $limit : null); + CLI::write(' Objects under prefix: ' . count($keys)); + CLI::write(' In this batch: ' . count($batch)); + + if ($batch === []) { + CLI::write(' Nothing to purge.', 'yellow'); + CLI::newLine(); + continue; + } + + foreach ($batch as $key) { + $totals['listed']++; + if ($dryRun) { + CLI::write(" - [dry-run] Would delete: {$key}"); + continue; + } + + $result = $s3->delete($key, $bucket); + if ($result['success'] ?? false) { + $totals['deleted']++; + CLI::write(" - Deleted: {$key}", 'green'); + } else { + $totals['failed']++; + CLI::write(' - Failed: ' . $key . ' | ' . ($result['message'] ?? ''), 'red'); + } + } + + CLI::newLine(); + } + + CLI::write('Purge summary', 'cyan'); + CLI::write(' Listed (batch): ' . $totals['listed']); + CLI::write(' Deleted: ' . $totals['deleted'], $totals['deleted'] > 0 ? 'green' : 'white'); + CLI::write(' Failed: ' . $totals['failed'], $totals['failed'] > 0 ? 'red' : 'white'); + + if ($dryRun) { + CLI::newLine(); + CLI::write('Dry-run complete. Re-run with --execute to delete these objects.', 'yellow'); + } + + return $totals['failed'] > 0 ? EXIT_ERROR : EXIT_SUCCESS; + } + + /** + * @return list + */ + private function parseModules(string $moduleOpt): array + { + $raw = array_filter(array_map('trim', explode(',', strtolower($moduleOpt)))); + $out = []; + foreach ($raw as $module) { + $module = basename(str_replace('\\', '/', $module)); + if ($module === '' || $module === '.' || $module === '..') { + continue; + } + if (strpos($module, '/') !== false || preg_match('/[^a-z0-9_\-]/i', $module)) { + CLI::write(" Skipping invalid module name: {$module}", 'yellow'); + continue; + } + $out[] = $module; + } + + return array_values(array_unique($out)); + } + + private function printKnownModules(): void + { + $uploadRoot = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads'; + CLI::write('Known local upload folders (S3 prefix = folder name):', 'cyan'); + $entries = @scandir($uploadRoot); + if (! is_array($entries)) { + CLI::write(' (uploads root missing)'); + return; + } + $names = []; + foreach ($entries as $entry) { + if ($entry === '.' || $entry === '..') { + continue; + } + if (is_dir($uploadRoot . DIRECTORY_SEPARATOR . $entry)) { + $names[] = $entry; + } + } + sort($names); + foreach ($names as $name) { + CLI::write(' ' . $name); + } + CLI::newLine(); + CLI::write('Example: php spark storage:purge-s3 --module claim_files'); + } +} diff --git a/app/Commands/StorageSmokeTest.php b/app/Commands/StorageSmokeTest.php new file mode 100644 index 00000000..0a13e5b0 --- /dev/null +++ b/app/Commands/StorageSmokeTest.php @@ -0,0 +1,514 @@ + 'One folder name, or all (default: all wired folders).', + '--list' => 'List every scenario ID without executing.', + ]; + + private int $pass = 0; + private int $fail = 0; + private int $skip = 0; + + /** @var list */ + private array $catalog = []; + + /** @var list */ + private array $wiredModules = [ + 'claim_files', + 'client_kyc_documents', + 'excel', + 'import_excel', + 'lead_files', + 'attachments', + 'claim_dump_excel', + 'claims_mis', + 'claims_dump', + 'statements', + 'bds_dump_excel', + 'hr_files', + 'non_eb_asset_files', + 'non_eb_rack_rate', + ]; + + public function run(array $params) + { + helper('utility'); + $this->buildCatalog(); + + if (CLI::getOption('list') !== null) { + $this->printCatalog(); + return EXIT_SUCCESS; + } + + $storage = \Config\Services::getFileStorageService(false); + $moduleOpt = strtolower((string) (CLI::getOption('module') ?? 'all')); + $modules = $moduleOpt === 'all' || $moduleOpt === '' + ? $this->wiredModules + : [basename($moduleOpt)]; + + CLI::write('========== STORAGE FULL SCENARIO RUN ==========', 'cyan'); + CLI::write('usesS3: ' . ($storage->usesS3() ? 'YES' : 'NO')); + CLI::write('Bucket: ' . ($storage->getBucket() ?? 'N/A')); + CLI::write('RETAIN_LOCAL: ' . (storage_env_retain_local() ? 'true' : 'false')); + CLI::write('Modules: ' . implode(', ', $modules)); + CLI::write('Total catalog scenarios: ' . count($this->catalog)); + CLI::newLine(); + + // ENV / helper checks + $this->runEnvScenarios($storage); + + // Per-module full lifecycle for every wired folder + foreach ($modules as $mod) { + $this->runModuleLifecycle($storage, $mod); + } + + // Cross-cutting claim / mail / mirror / soft-delete + $this->runClaimScenarios($storage); + $this->runMailScenarios($storage); + $this->runMirrorScenario($storage); + $this->runRetainLocalSimulation($storage); + $this->runClaimsDumpResolve($storage); + + CLI::newLine(); + CLI::write( + sprintf('RESULT: %d passed | %d failed | %d skipped', $this->pass, $this->fail, $this->skip), + $this->fail === 0 ? 'green' : 'red' + ); + + CLI::newLine(); + $this->printManualOnlyRemaining(); + + return $this->fail > 0 ? EXIT_ERROR : EXIT_SUCCESS; + } + + private function buildCatalog(): void + { + $this->catalog = [ + ['id' => 'ENV-01', 'group' => 'ENV', 'title' => 'FILE_STORAGE / bucket / RETAIN_LOCAL readable'], + ['id' => 'ENV-02', 'group' => 'ENV', 'title' => 'Runtime temp dirs writable'], + ['id' => 'ENV-03', 'group' => 'ENV', 'title' => 'usesS3 matches FILE_STORAGE_DRIVER'], + ]; + + foreach ($this->wiredModules as $mod) { + $this->catalog[] = ['id' => "MOD-{$mod}-01", 'group' => "MODULE:{$mod}", 'title' => 'Upload to S3/local']; + $this->catalog[] = ['id' => "MOD-{$mod}-02", 'group' => "MODULE:{$mod}", 'title' => 'Exists after upload']; + $this->catalog[] = ['id' => "MOD-{$mod}-03", 'group' => "MODULE:{$mod}", 'title' => 'Download content']; + $this->catalog[] = ['id' => "MOD-{$mod}-04", 'group' => "MODULE:{$mod}", 'title' => 'ensure_local from storage']; + $this->catalog[] = ['id' => "MOD-{$mod}-05", 'group' => "MODULE:{$mod}", 'title' => 'No permanent local when RETAIN_LOCAL=false (S3)']; + $this->catalog[] = ['id' => "MOD-{$mod}-06", 'group' => "MODULE:{$mod}", 'title' => 'Delete removes object']; + $this->catalog[] = ['id' => "MOD-{$mod}-07", 'group' => "MODULE:{$mod}", 'title' => 'Presigned URL (S3 only)']; + } + + $extra = [ + ['id' => 'CLAIM-01', 'group' => 'CLAIM', 'title' => 'resolve_claim_file_path'], + ['id' => 'CLAIM-02', 'group' => 'CLAIM', 'title' => 'claim_file_download_url (TPA)'], + ['id' => 'CLAIM-03', 'group' => 'CLAIM', 'title' => 'soft_delete cleans S3 when unused (DB)'], + ['id' => 'MERGE-PDF', 'group' => 'CLAIM', 'title' => 'Dedicated PDF merge suite — php spark storage:smoke-test-merge-pdf'], + ['id' => 'MAIL-01', 'group' => 'MAIL', 'title' => 'resolve_mail_attachment_path'], + ['id' => 'GEN-01', 'group' => 'GENERATED', 'title' => 'mirror_generated_file then local gone'], + ['id' => 'RETAIN-01', 'group' => 'RETAIN_LOCAL', 'title' => 'Simulate retain=false (S3-only)'], + ['id' => 'RETAIN-02', 'group' => 'RETAIN_LOCAL', 'title' => 'Simulate retain=true (dual-write keep local)'], + ['id' => 'DUMP-01', 'group' => 'CLAIMS_DUMP', 'title' => 'resolveDumpLocalPath / ensure_local'], + ]; + foreach ($extra as $row) { + $this->catalog[] = $row; + } + } + + private function printCatalog(): void + { + CLI::write('========== ALL SCENARIO TEST CASES ==========', 'cyan'); + $current = ''; + foreach ($this->catalog as $row) { + if ($row['group'] !== $current) { + $current = $row['group']; + CLI::newLine(); + CLI::write("--- {$current} ---", 'yellow'); + } + CLI::write(" {$row['id']} {$row['title']}"); + } + CLI::newLine(); + CLI::write('Manual-only (UI/TPA jobs — run by human after automated):', 'yellow'); + foreach ($this->manualScenarioLines() as $line) { + CLI::write(' ' . $line); + } + CLI::newLine(); + CLI::write('Run all automated: php spark storage:smoke-test'); + } + + private function step(string $id, bool $ok, string $detail = ''): void + { + $title = $this->titleFor($id); + if ($ok) { + $this->pass++; + CLI::write("[PASS] {$id} {$title}" . ($detail !== '' ? " — {$detail}" : ''), 'green'); + } else { + $this->fail++; + CLI::write("[FAIL] {$id} {$title}" . ($detail !== '' ? " — {$detail}" : ''), 'red'); + } + } + + private function skip(string $id, string $reason): void + { + $this->skip++; + CLI::write("[SKIP] {$id} {$this->titleFor($id)} — {$reason}", 'yellow'); + } + + private function titleFor(string $id): string + { + foreach ($this->catalog as $row) { + if ($row['id'] === $id) { + return $row['title']; + } + } + + return $id; + } + + private function uploadPath(string $mod): string + { + return rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $mod; + } + + private function makeTmp(string $name, string $body): string + { + $dir = storage_runtime_temp_dir(); + if (! is_dir($dir)) { + mkdir($dir, 0755, true); + } + $path = $dir . DIRECTORY_SEPARATOR . $name; + file_put_contents($path, $body); + + return $path; + } + + private function runEnvScenarios($storage): void + { + CLI::write('--- ENV ---', 'white'); + $driver = strtolower((string) (getenv('FILE_STORAGE_DRIVER') ?: env('FILE_STORAGE_DRIVER') ?: '')); + $bucket = (string) (getenv('AWS_FILE_UPLOAD_BUCKET') ?: getenv('AWS_BUCKET') ?: ''); + $this->step('ENV-01', true, "driver=" . ($driver !== '' ? $driver : 'default') . ", retain=" . (storage_env_retain_local() ? '1' : '0') . ", bucket=" . ($bucket !== '' ? 'set' : 'empty')); + + $runtime = storage_runtime_temp_dir(); + $claimRt = rtrim(WRITEPATH, '/\\') . '/cache/claim_files_runtime'; + foreach ([$runtime, $claimRt] as $d) { + if (! is_dir($d)) { + @mkdir($d, 0755, true); + } + } + $probe = $runtime . DIRECTORY_SEPARATOR . 'probe_' . uniqid('', true) . '.txt'; + $wrote = @file_put_contents($probe, 'ok') !== false; + if ($wrote) { + @unlink($probe); + } + $this->step('ENV-02', $wrote && is_dir($runtime)); + + if ($driver === 'local') { + $this->step('ENV-03', ! $storage->usesS3(), 'local driver => usesS3 false'); + } elseif ($driver === 's3' || $driver === '') { + $this->step('ENV-03', $storage->usesS3(), 's3 driver => usesS3 true (bucket required)'); + } else { + $this->skip('ENV-03', 'unknown FILE_STORAGE_DRIVER=' . $driver); + } + } + + private function runModuleLifecycle($storage, string $mod): void + { + CLI::write("--- MODULE: {$mod} ---", 'white'); + $path = $this->uploadPath($mod); + $fileName = 'smoke_' . $mod . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(2)) . '.txt'; + $tmp = $this->makeTmp($fileName, "smoke-{$mod}\n" . date('c') . "\n"); + + $up = $storage->upload($tmp, $path, $fileName); + @unlink($tmp); + $this->step("MOD-{$mod}-01", (bool) ($up['success'] ?? false), (string) ($up['message'] ?? '')); + if (! ($up['success'] ?? false)) { + // Skip rest of module if upload failed + foreach (['02', '03', '04', '05', '06', '07'] as $n) { + $this->skip("MOD-{$mod}-{$n}", 'upload failed'); + } + return; + } + + $this->step("MOD-{$mod}-02", $storage->exists($path, $fileName)); + + $dl = $storage->download($path, $fileName); + $contentOk = ($dl['success'] ?? false) && ( + ((! empty($dl['content'])) && str_contains((string) $dl['content'], "smoke-{$mod}")) + || ((! empty($dl['path'])) && is_file((string) $dl['path']) + && str_contains((string) file_get_contents((string) $dl['path']), "smoke-{$mod}")) + ); + $this->step("MOD-{$mod}-03", $contentOk, (string) ($dl['message'] ?? '')); + + // Force missing permanent local then ensure_local + $permanent = $storage->resolveLocalPath($path, $fileName); + if (is_file($permanent)) { + @unlink($permanent); + } + $ensured = storage_ensure_local_file($path, $fileName); + $this->step( + "MOD-{$mod}-04", + is_string($ensured) && is_file($ensured) && str_contains((string) file_get_contents($ensured), "smoke-{$mod}"), + $ensured ? basename($ensured) : 'null' + ); + if ($ensured) { + storage_cleanup_temp_file($ensured); + } + + if ($storage->usesS3() && ! storage_env_retain_local()) { + $this->step("MOD-{$mod}-05", ! is_file($permanent), 'permanent path absent'); + } else { + $this->skip("MOD-{$mod}-05", $storage->usesS3() ? 'RETAIN_LOCAL=true' : 'local driver'); + } + + $del = $storage->delete($path, $fileName); + $this->step("MOD-{$mod}-06", (bool) ($del['success'] ?? false) && ! $storage->exists($path, $fileName)); + + if ($storage->usesS3()) { + $tmp = $this->makeTmp($fileName, "presign-{$mod}\n"); + $storage->upload($tmp, $path, $fileName); + @unlink($tmp); + $pre = $storage->getPresignedUrl($path, $fileName, 5); + $this->step("MOD-{$mod}-07", (bool) ($pre['success'] ?? false) && ! empty($pre['url'])); + $storage->delete($path, $fileName); + } else { + $this->skip("MOD-{$mod}-07", 'local driver'); + } + } + + private function runClaimScenarios($storage): void + { + CLI::write('--- CLAIM ---', 'white'); + $claimDir = $this->uploadPath('claim_files'); + $fileName = 'smoke_claim_' . date('Ymd_His') . '_' . bin2hex(random_bytes(2)) . '.pdf'; + $tmp = $this->makeTmp($fileName, "%PDF-1.4 smoke-claim\n"); + $up = $storage->upload($tmp, $claimDir, $fileName); + @unlink($tmp); + if (! ($up['success'] ?? false)) { + $this->skip('CLAIM-01', 'upload claim sample failed'); + $this->skip('CLAIM-02', 'upload claim sample failed'); + $this->skip('CLAIM-03', 'upload claim sample failed'); + return; + } + // Ensure no permanent leftover interferes + $permanent = $storage->resolveLocalPath($claimDir, $fileName); + if (is_file($permanent)) { + @unlink($permanent); + } + + $resolved = storage_resolve_claim_file_path($claimDir, $fileName); + $this->step( + 'CLAIM-01', + (bool) ($resolved['success'] ?? false) && ! empty($resolved['path']) && is_file((string) $resolved['path']), + (string) ($resolved['message'] ?? '') + ); + storage_cleanup_temp_claim_file($resolved); + + if ($storage->usesS3()) { + $url = storage_claim_file_download_url($fileName, null, 10); + $this->step('CLAIM-02', is_string($url) && str_starts_with($url, 'http')); + } else { + $this->skip('CLAIM-02', 'local driver'); + } + + // Soft-delete: insert temp claim_files row, soft delete, expect S3 gone + try { + $db = \Config\Database::connect(); + $insert = [ + 'ticket_master_id' => 0, + 'file_name' => $fileName, + 'url' => $fileName, + 'doc_name' => 'smoke-test', + 'file_type' => 2, + 'is_active' => 1, + 'created_at' => date('Y-m-d H:i:s'), + ]; + // Only use columns that exist + $fields = $db->getFieldNames('claim_files'); + $row = []; + foreach ($insert as $k => $v) { + if (in_array($k, $fields, true)) { + $row[$k] = $v; + } + } + $db->table('claim_files')->insert($row); + $id = (int) $db->insertID(); + $ok = $id > 0 && storage_soft_delete_claim_file($id); + $gone = ! $storage->exists($claimDir, $fileName); + $this->step('CLAIM-03', $ok && $gone, 'id=' . $id); + // Hard cleanup leftover row + if ($id > 0) { + $db->table('claim_files')->where('id', $id)->delete(); + } + } catch (\Throwable $e) { + $this->skip('CLAIM-03', 'DB soft-delete unavailable: ' . $e->getMessage()); + $storage->delete($claimDir, $fileName); + } + } + + private function runMailScenarios($storage): void + { + CLI::write('--- MAIL ---', 'white'); + $dir = $this->uploadPath('attachments'); + $fileName = 'smoke_mail_' . date('Ymd_His') . '_' . bin2hex(random_bytes(2)) . '.txt'; + $tmp = $this->makeTmp($fileName, "mail\n"); + $up = $storage->upload($tmp, $dir, $fileName); + @unlink($tmp); + if (! ($up['success'] ?? false)) { + $this->step('MAIL-01', false, (string) ($up['message'] ?? '')); + return; + } + $permanent = $storage->resolveLocalPath($dir, $fileName); + if (is_file($permanent)) { + @unlink($permanent); + } + $resolved = storage_resolve_mail_attachment_path('uploads/attachments/' . $fileName, $fileName); + $this->step( + 'MAIL-01', + (bool) ($resolved['success'] ?? false) && ! empty($resolved['path']) && is_file((string) $resolved['path']) + ); + if (! empty($resolved['path'])) { + storage_cleanup_temp_file((string) $resolved['path']); + } + $storage->delete($dir, $fileName); + } + + private function runMirrorScenario($storage): void + { + CLI::write('--- GENERATED ---', 'white'); + $dir = $this->uploadPath('excel'); + $fileName = 'smoke_gen_' . date('Ymd_His') . '_' . bin2hex(random_bytes(2)) . '.txt'; + $local = $this->makeTmp($fileName, "generated\n"); + $ok = storage_mirror_generated_file($local, $dir, $fileName); + if ($storage->usesS3()) { + $this->step('GEN-01', $ok && ! is_file($local) && $storage->exists($dir, $fileName)); + $storage->delete($dir, $fileName); + } else { + $this->step('GEN-01', $ok && is_file($local)); + @unlink($local); + } + } + + private function runRetainLocalSimulation($storage): void + { + CLI::write('--- RETAIN_LOCAL simulation ---', 'white'); + if (! $storage->usesS3()) { + $this->skip('RETAIN-01', 'local driver'); + $this->skip('RETAIN-02', 'local driver'); + return; + } + + $dir = $this->uploadPath('excel'); + + // retain=false behaviour: upload via temp then unlink (current default path) + $f1 = 'smoke_retain_off_' . bin2hex(random_bytes(2)) . '.txt'; + $t1 = $this->makeTmp($f1, "off\n"); + $storage->upload($t1, $dir, $f1); + @unlink($t1); + $p1 = $storage->resolveLocalPath($dir, $f1); + $this->step('RETAIN-01', ! is_file($p1) && $storage->exists($dir, $f1)); + $storage->delete($dir, $f1); + + // retain=true behaviour: keep local file after S3 upload + $f2 = 'smoke_retain_on_' . bin2hex(random_bytes(2)) . '.txt'; + // Use runtime path as the "kept local" copy (CLI may lack write perms under uploads/) + $p2 = $this->makeTmp($f2, "on\n"); + $up2 = $storage->upload($p2, $dir, $f2); + $this->step( + 'RETAIN-02', + (bool) ($up2['success'] ?? false) && is_file($p2) && $storage->exists($dir, $f2), + 'local kept + S3' + ); + @unlink($p2); + $storage->delete($dir, $f2); + } + + private function runClaimsDumpResolve($storage): void + { + CLI::write('--- CLAIMS_DUMP ---', 'white'); + $dir = $this->uploadPath('claims_dump'); + $fileName = 'smoke_cdump_' . date('Ymd_His') . '_' . bin2hex(random_bytes(2)) . '.xlsx'; + $tmp = $this->makeTmp($fileName, "claims-dump\n"); + $up = $storage->upload($tmp, $dir, $fileName); + @unlink($tmp); + if (! ($up['success'] ?? false)) { + $this->step('DUMP-01', false, (string) ($up['message'] ?? '')); + return; + } + $permanent = $storage->resolveLocalPath($dir, $fileName); + if (is_file($permanent)) { + @unlink($permanent); + } + $resolved = storage_ensure_local_file($dir, $fileName); + $this->step('DUMP-01', is_string($resolved) && is_file($resolved)); + if ($resolved) { + storage_cleanup_temp_file($resolved); + } + $storage->delete($dir, $fileName); + } + + /** @return list */ + private function manualScenarioLines(): array + { + return [ + 'UI-KYC-01 Upload KYC in browser', + 'UI-KYC-02 Download download-kyc-docs/{id}', + 'UI-KYC-03 Re-upload / delete KYC', + 'UI-PT-01 Upload PT doc + download-pt-docs/{id}', + 'UI-PT-02 REST downloadPolicyFiles', + 'UI-CLAIM-01 Upload claim file in ticket UI', + 'UI-CLAIM-02 downloadClaimFile / viewClaimFile', + 'UI-CLAIM-03 Remove claim file in UI', + 'UI-CLAIM-04 Merge PDFs in UI (core automated: php spark storage:smoke-test-merge-pdf)', + 'UI-TPA-01 FHPL claim submit (base64)', + 'UI-TPA-02 Vidal claim submit (base64)', + 'UI-TPA-03 HealthIndia claim submit', + 'UI-TPA-04 MediAssist uses presigned URL', + 'UI-TPA-05 Volo uses presigned URL', + 'UI-EMP-01 Employee excel upload + background job', + 'UI-EMP-02 download-file-list / download_import_file', + 'UI-EMP-03 HR upload + hrFileDownload', + 'UI-LEAD-01 Lead member upload + demography job', + 'UI-LEAD-02 downloadMemberFile / error excel', + 'UI-DUMP-01 Claim dump / MIS / statements / BDS UI jobs', + 'UI-MAIL-01 Compose mail with attachment send', + 'UI-NEB-01 Non-EB asset + rack rate UI', + 'UI-NEG-01 Invalid extension rejected in UI', + 'UI-NEG-02 Toggle FILE_STORAGE_DRIVER=local and retest one upload', + 'UI-NEG-03 Toggle RETAIN_LOCAL=true/false and retest one upload', + ]; + } + + private function printManualOnlyRemaining(): void + { + CLI::write('========== MANUAL UI / TPA / JOB (not auto-runnable) ==========', 'yellow'); + CLI::write('These need browser login / real TPA — tick after you test:'); + foreach ($this->manualScenarioLines() as $i => $line) { + CLI::write(sprintf(' [ ] %s', $line)); + } + CLI::newLine(); + CLI::write('Commands:'); + CLI::write(' php spark storage:smoke-test # run ALL automated scenarios'); + CLI::write(' php spark storage:smoke-test --list # list every scenario ID'); + CLI::write(' php spark storage:smoke-test --module claim_files'); + } +} diff --git a/app/Config/Routes.php b/app/Config/Routes.php index d2df5889..52915d2b 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -90,6 +90,7 @@ $routes->get('/auth/google', 'LoginController::initiateGoogleOAuth'); $routes->get('/update-emp-policy-status', 'ClientController::updateEmpAndPolicyStatus'); $routes->get('download-e-card/(:any)', 'EmployeeController::generateIDCardForEmployee/$1'); $routes->get('download-kyc-docs/(:segment)', 'ClientController::downloadKYCDocument/$1'); +$routes->get('download-pt-docs/(:segment)', 'PolicyTransactionController::downloadPtDocument/$1'); $routes->get('claim-form-download/(:any)', 'TicketController::downloadClaimForm/$1'); $routes->get('downloadClaimFile/(:any)', 'TicketController::downloadClaimFile/$1'); $routes->get('viewClaimFile/(:any)', 'TicketController::viewClaimFile/$1'); @@ -446,6 +447,8 @@ $routes->group("/util", ["filter" => "authMVC"], function ($routes) { $routes->get('getClaimDumpFileErrorData', 'TicketController::getClaimDumpFileErrorData'); $routes->get("claim_dump_excel_error/(:any)", "TicketController::getClaimDumpExcelFileErrors/$1"); $routes->get("download_claim_dump_file/(:any)", "TicketController::downloadClaimDumpFile/$1"); + $routes->post('claims-dump/upload', 'ClaimsUploadController::uploadDump'); + $routes->get('claims-dump/download/(:num)', 'ClaimsUploadController::downloadDump/$1'); $routes->match(['get', 'post'], 'truncateClaimDumpFile', 'TicketController::truncateClaimDumpFile'); $routes->match(['get', 'post'], 'reprocessClaimDumpPending', 'TicketController::reprocessClaimDumpPending'); $routes->match(['get', 'post'], 'getClaimDumpPendingRows', 'TicketController::getClaimDumpPendingRows'); diff --git a/app/Config/Services.php b/app/Config/Services.php index 73d75683..df7e3dfd 100755 --- a/app/Config/Services.php +++ b/app/Config/Services.php @@ -10,6 +10,7 @@ use App\Libraries\MyGoogleDrive; use App\Libraries\RuleImportService; use App\Libraries\DataServiceSqlite; use App\Libraries\S3Service; +use App\Libraries\FileStorageService; use App\Controllers\Home; /** @@ -100,5 +101,14 @@ class Services extends BaseService return new S3Service(); } + + public static function getFileStorageService($getShared = true) + { + if ($getShared) { + return static::getSharedInstance('getFileStorageService'); + } + + return new FileStorageService(); + } } diff --git a/app/Controllers/Api/NonEbClaimApiController.php b/app/Controllers/Api/NonEbClaimApiController.php index 0e2355b5..5876888d 100644 --- a/app/Controllers/Api/NonEbClaimApiController.php +++ b/app/Controllers/Api/NonEbClaimApiController.php @@ -142,7 +142,7 @@ class NonEbClaimApiController extends BaseController return null; } $uploadPath = WRITEPATH . 'uploads/non_eb_asset_files/'; - $fileName = file_Upload($file, $uploadPath, UPLOAD_EXT_ASSET_FILES); + $fileName = storage_file_Upload($file, $uploadPath, UPLOAD_EXT_ASSET_FILES); return !empty($fileName) ? $fileName : null; } @@ -323,7 +323,7 @@ class NonEbClaimApiController extends BaseController } $uploadPath = WRITEPATH . 'uploads/non_eb_asset_files/'; - $assetFileName = file_Upload($assetFile, $uploadPath, $allowed); + $assetFileName = storage_file_Upload($assetFile, $uploadPath, $allowed); if (empty($assetFileName)) { return $this->respond(['status' => false, 'code' => 500, 'message' => 'Asset file upload failed'], 500); @@ -675,7 +675,7 @@ class NonEbClaimApiController extends BaseController $file_data = []; if (!empty($get_file_data)) { $file_path = WRITEPATH . 'uploads/claim_files/'; - $file_data = multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); + $file_data = storage_multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); } if (!empty($file_data) && !empty($ticket_id)) { @@ -775,22 +775,25 @@ class NonEbClaimApiController extends BaseController $db->transStart(); $upload_path = WRITEPATH . 'uploads/claim_files/'; - $file_name = file_Upload($file, $upload_path, UPLOAD_EXT_CLAIM_DOCS); + $uploaded = storage_claim_file_Upload($file, $upload_path, UPLOAD_EXT_CLAIM_DOCS); - if (empty($file_name)) { + if ($uploaded === '') { $db->transRollback(); return $this->respond(['status' => false, 'code' => 500, 'message' => 'File upload failed'], 500); } + $disk_name = $uploaded['disk_name']; + $display_name = $uploaded['display_name']; + // Insert into claim_files $this->claimFilesModel->insert([ 'ticket_id' => $claim_id, 'ticket_type' => 2, 'doc_name' => $document_name, - 'file_name' => $file_name, - 'url' => $upload_path . $file_name, + 'file_name' => $display_name, + 'url' => $disk_name, 'file_type' => 2, - 'mime_type' => getMimeTypeByFileName($file_name), + 'mime_type' => getMimeTypeByFileName($display_name), 'is_active' => 1, 'created_by' => self::API_SYSTEM_USER_ID, ]); @@ -811,7 +814,7 @@ class NonEbClaimApiController extends BaseController $file_id = $this->claimFilesModel->insertID(); $download_url = base_url('downloadClaimFile/') . $file_id; - $this->myLogger->logme('error', "[NON_EB_API][uploadRequiredDoc] Doc uploaded. claim_id: $claim_id, doc: $document_name, file: $file_name, user: {$authUser['id']}"); + $this->myLogger->logme('error', "[NON_EB_API][uploadRequiredDoc] Doc uploaded. claim_id: $claim_id, doc: $document_name, file: $disk_name, user: {$authUser['id']}"); return $this->respond([ 'status' => true, diff --git a/app/Controllers/ClaimsUploadController.php b/app/Controllers/ClaimsUploadController.php index a704ba3b..a7c92d88 100644 --- a/app/Controllers/ClaimsUploadController.php +++ b/app/Controllers/ClaimsUploadController.php @@ -33,23 +33,78 @@ class ClaimsUploadController extends BaseController ], 400); } + $uploadFile = storage_file_Upload($file, WRITEPATH . 'uploads/claims_dump/', UPLOAD_EXT_EXCEL); + if ($uploadFile === '') { + return $this->respond([ + 'status' => 'failed', + 'message' => 'File upload failed' + ], 500); + } + $data = [ 'client_id' => $this->request->getPost('client_id'), 'tpa_id' => $this->request->getPost('tpa_id'), 'client_policy_id' => $this->request->getPost('client_policy_id'), 'from_date' => $this->request->getPost('from_date'), 'to_date' => $this->request->getPost('to_date'), - 'upload_file' => $file->getRandomName(), + 'upload_file' => $uploadFile, // 'uploaded_by' => user_id() ]; - $file->move(WRITEPATH . 'uploads/claims_dump', $data['upload_file']); - $this->db->table('claims_dump_uploads')->insert($data); + $insertId = $this->db->insertID(); return $this->respond([ 'status' => 'success', - 'message' => 'Claims dump uploaded' + 'message' => 'Claims dump uploaded', + 'id' => $insertId, + 'upload_file' => $uploadFile, ]); } + + /** + * Download a claims_dump upload by claims_dump_uploads.id (local or S3). + */ + public function downloadDump($id = null) + { + $id = (int) ($id ?? $this->request->getGet('id')); + if ($id <= 0) { + $data['message'] = 'Invalid file id'; + return view('errors/404', $data); + } + + $record = $this->db->table('claims_dump_uploads')->where('id', $id)->get()->getRowArray(); + if (empty($record) || empty($record['upload_file'])) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } + + $fileName = basename((string) $record['upload_file']); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download(WRITEPATH . 'uploads/claims_dump', $fileName); + + if (! ($result['success'] ?? false)) { + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); + } + + $downloadAs = storage_upload_display_name($fileName); + 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); + } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); + } + + /** + * Resolve claims_dump file to a local path for any processors (local first, else S3). + */ + public function resolveDumpLocalPath(string $fileName): ?string + { + return storage_ensure_local_file(WRITEPATH . 'uploads/claims_dump', $fileName); + } } diff --git a/app/Controllers/ClientController.php b/app/Controllers/ClientController.php index d64146ee..182f9f22 100755 --- a/app/Controllers/ClientController.php +++ b/app/Controllers/ClientController.php @@ -1311,7 +1311,7 @@ class ClientController extends AdminController // print_r($data); die; unset($data['file_name']); $uploadFilePath = WRITEPATH . 'uploads/client_kyc_documents'; - $File = file_Upload($this->request->getFile('file_name'), $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + $File = storage_file_Upload($this->request->getFile('file_name'), $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if (!empty($File)) { $data['file_name'] = $File; @@ -1321,7 +1321,7 @@ class ClientController extends AdminController } $data['created_by'] = get_session_userid(); - $insert = $this->clientKYCDocsModel->insert($data); + $insert = $this->insertPrimaryKycDocument($data); if ($insert) { // $kycDocs = $this->clientKYCDocsModel->where('client_id', $this->request->getPost('client_id'))->findAll(); if ($form_type == "others") { @@ -1414,7 +1414,7 @@ class ClientController extends AdminController } foreach ($uploadedFiles as $index => $singleFile) { - $fileName = file_Upload_for_lead($singleFile, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + $fileName = storage_file_Upload($singleFile, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if (empty($fileName)) { return $this->respond(['status' => false, 'code' => 404, 'message' => 'Failed to upload KYC document'], 200); } @@ -1459,14 +1459,14 @@ class ClientController extends AdminController } $file = $this->request->getFile('file_name'); - $fileName = file_Upload_for_lead($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + $fileName = storage_file_Upload($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if (!empty($fileName)) { $sanitized_data['file_name'] = $fileName; } $sanitized_data['created_by'] = get_session_userid(); - $insertID = $this->clientKYCDocsModel->insert($sanitized_data); + $insertID = $this->insertPrimaryKycDocument($sanitized_data); if ($insertID) { $kycDocs = $this->generateKycPrimaryTable($client_id); @@ -1510,7 +1510,7 @@ class ClientController extends AdminController $file = $this->request->getFile('file_name'); - $fileName = file_Upload_for_lead($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + $fileName = storage_file_Upload($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if (!empty($fileName)) { $sanitized_data['file_name'] = $fileName; @@ -1518,9 +1518,10 @@ class ClientController extends AdminController $id = $sanitized_data['PrimaryKey']; $sanitized_data['client_id'] = $id; $sanitized_data['kyc_doc_type_id'] = $this->request->getPost('kyc_doc_id'); + $sanitized_data['created_by'] = get_session_userid(); $sanitized_data['updated_by'] = get_session_userid(); - $insertID = $this->clientKYCDocsModel->insert($sanitized_data); + $insertID = $this->insertPrimaryKycDocument($sanitized_data); if ($insertID) { if ($form_type === 'others') { $kycDocs = $this->generateKycOthersTable($sanitized_data['client_id']); @@ -1536,12 +1537,7 @@ class ClientController extends AdminController public function deleteClientKycDocs($id = null) { $client_id = $this->request->getGet('client_id'); - $updateData = [ - 'is_active' => 0, - 'updated_by' => get_session_userid() - ]; - - $delete = $this->clientKYCDocsModel->update($id, $updateData); + $delete = $this->softDeleteKycDocument((int) $id); if ($delete) { $response = ['status' => true, 'code' => 200, 'id' => $id]; if (!empty($client_id)) { @@ -1555,12 +1551,7 @@ class ClientController extends AdminController public function deleteClientKycOtherDocs($id = null) { $client_id = $this->request->getGet('client_id'); - $updateData = [ - 'is_active' => 0, - 'updated_by' => get_session_userid() - ]; - - $delete = $this->clientKYCDocsModel->update($id, $updateData); + $delete = $this->softDeleteKycDocument((int) $id); if ($delete) { $response = ['status' => true, 'code' => 200, 'id' => $id]; if (!empty($client_id)) { @@ -1613,7 +1604,7 @@ class ClientController extends AdminController $file = $this->request->getFile('file_name'); $uploadFilePath = WRITEPATH . 'uploads/client_kyc_documents'; - $fileName = file_Upload($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + $fileName = storage_file_Upload($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if (!empty($fileName)) { $sanitized_data['file_name'] = $fileName; @@ -1626,7 +1617,14 @@ class ClientController extends AdminController $this->myLogger->logme('info', 'Result of file_Upload: ' . $fileName); unset($data['file_name']); - $insertID = $this->clientKYCDocsModel->insert($sanitized_data); + // Primary typed docs: replace previous active upload for same type. + // Additional docs (kyc_doc_type_id empty/0) still allow multiple rows. + $kycDocTypeId = (int) ($sanitized_data['kyc_doc_type_id'] ?? 0); + if ($kycDocTypeId > 0) { + $insertID = $this->insertPrimaryKycDocument($sanitized_data); + } else { + $insertID = $this->clientKYCDocsModel->insert($sanitized_data); + } if ($insertID) { $html = $this->generateKycSingleTable($sanitized_data['client_id']); @@ -1685,21 +1683,11 @@ class ClientController extends AdminController if ($uploadedFile && $uploadedFile->isValid() && !$uploadedFile->hasMoved()) { - $new_file_name = file_Upload($uploadedFile, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + $new_file_name = storage_file_Upload($uploadedFile, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if (!$new_file_name) { return $this->respond(['status' => false, 'code' => 500, 'message' => 'New file upload failed on server.'], 200); } $updateData['file_name'] = $new_file_name; - - // Delete the old file from the storage if it exists - // if (!empty($old_file_name)) { - // $old_file_path = $uploadFilePath . '/' . $old_file_name; - // if (file_exists($old_file_path)) { - // unlink($old_file_path); - // // Optionally delete from G-Drive here if applicable - // } - // } - } if (empty($updateData)) { @@ -1711,6 +1699,19 @@ class ClientController extends AdminController $update = $this->clientKYCDocsModel->update($kyc_id, $updateData); if ($update) { + // After successful replace, remove previous storage object when unused. + $oldStored = basename((string) $old_file_name); + if ($oldStored !== '' && !empty($new_file_name) && $oldStored !== $new_file_name) { + $stillUsed = $this->clientKYCDocsModel + ->where('file_name', $oldStored) + ->where('is_active', 1) + ->countAllResults(); + if ($stillUsed === 0) { + $storage = \Config\Services::getFileStorageService(); + $storage->delete($uploadFilePath, $oldStored); + } + } + return $this->respond([ 'status' => true, 'message' => 'Document updated successfully', @@ -1736,13 +1737,28 @@ class ClientController extends AdminController return $this->respond(['status' => false, 'code' => 400, 'message' => 'Missing document ID.'], 200); } - + // Soft-delete path (also removes unused storage object) + if ((int) $is_active === 0) { + $delete = $this->softDeleteKycDocument((int) $kyc_id); + if ($delete) { + return $this->respond([ + 'status' => true, + 'code' => 200, + 'id' => $kyc_id, + 'message' => 'Document successfully deactivated.', + 'html' => $this->generateKycSingleTable($client_id), + 'dropdown' => $this->fetch_dropdown($client_id) + ], 200); + } + + return $this->respond(['status' => false, 'code' => 404, 'message' => 'Failed to update record (ID not found or DB error).'], 200); + } + $updateData = [ 'is_active' => (int) $is_active, 'updated_by' => get_session_userid() ]; - $delete = $this->clientKYCDocsModel->update($kyc_id, $updateData); if ($delete) { @@ -3364,13 +3380,14 @@ class ClientController extends AdminController } $uploadFilePath = WRITEPATH . 'uploads/non_eb_rack_rate'; - $fileName = file_Upload($this->request->getFile('file'), $uploadFilePath, UPLOAD_EXT_NON_EB_RACK_RATE); + $uploadedFile = $this->request->getFile('file'); + $originalName = $uploadedFile ? $uploadedFile->getClientName() : ''; + $fileName = storage_file_Upload($uploadedFile, $uploadFilePath, UPLOAD_EXT_NON_EB_RACK_RATE); if (empty($fileName)) { return $this->respond(['status' => false, 'message' => 'Invalid file. Only PDF and Excel files are allowed.'], 200); } - $originalName = $this->request->getFile('file')->getClientName(); $ext = pathinfo($fileName, PATHINFO_EXTENSION); $jsonField = is_array($policy) ? ($policy['non_eb_rack_rate_files'] ?? null) : ($policy->non_eb_rack_rate_files ?? null); @@ -3378,7 +3395,7 @@ class ClientController extends AdminController $fileEntry = [ 'name' => $fileName, - 'original_name' => $originalName, + 'original_name' => $originalName ?: storage_upload_display_name($fileName), 'type' => $ext, 'uploaded_at' => date('Y-m-d H:i:s'), ]; @@ -3409,10 +3426,11 @@ class ClientController extends AdminController $this->clientPolicyModel->where('id', $clientPolicyId)->set(['non_eb_rack_rate_files' => json_encode($files)])->update(); - // Delete physical file - $filePath = WRITEPATH . 'uploads/non_eb_rack_rate/' . $filename; - if (file_exists($filePath)) { - unlink($filePath); + // Delete from S3/local storage + $storedName = basename((string) $filename); + if ($storedName !== '') { + $storage = \Config\Services::getFileStorageService(); + $storage->delete(WRITEPATH . 'uploads/non_eb_rack_rate', $storedName); } return $this->respond(['status' => true], 200); @@ -3795,8 +3813,8 @@ class ClientController extends AdminController $file = $files['file_name'][$key]; if (!empty($docName) && $file->isValid() && !$file->hasMoved()) { - // Upload the file - $uploadedFileName = file_Upload_for_lead($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); + // Upload the file (unique storage key; safe for S3 + duplicate names) + $uploadedFileName = storage_file_Upload($file, $uploadFilePath, UPLOAD_EXT_KYC_DOCS); if ($uploadedFileName) { // Prepare data for each document upload @@ -3856,30 +3874,52 @@ class ClientController extends AdminController public function generateKycPrimaryTable($client_id) { - + $clientId = (int) $client_id; $db = db_connect(); - $builder = $db->table('kyc_docs kd'); - $builder->select('kd.*, ck.file_name AS upload_doc_name, ck.id AS client_kyc_id'); - $builder->join('clients c', 'kd.kyc_type_id = c.entity_type_id'); - $builder->join( - 'client_kyc_documents ck', - 'kd.id = ck.kyc_doc_type_id AND ck.client_id = c.id AND ck.is_active = 1', - 'left' - ); - $builder->where('c.id', $client_id); - $builder->where('kd.is_active',1); - $builder->groupBy(['kd.id', 'ck.file_name']); + // Required document types for this client's entity. + $docs = $db->table('kyc_docs kd') + ->select('kd.*') + ->join('clients c', 'kd.kyc_type_id = c.entity_type_id') + ->where('c.id', $clientId) + ->where('kd.is_active', 1) + ->groupBy('kd.id') + ->orderBy('kd.id', 'ASC') + ->get() + ->getResultArray(); - $query = $builder->get(); - $result['data'] = $query->getResultArray(); - $result['client_id'] = $client_id; + // Latest active upload per doc type (prevents duplicate Status rows). + $uploads = $this->clientKYCDocsModel + ->select('id, kyc_doc_type_id, file_name') + ->where('client_id', $clientId) + ->where('is_active', 1) + ->where('kyc_doc_type_id IS NOT NULL', null, false) + ->where('kyc_doc_type_id !=', 0) + ->orderBy('id', 'DESC') + ->findAll(); - $table = view('client_kyc_primary_table', $result); + $latestByType = []; + foreach ($uploads as $upload) { + $typeId = (int) ($upload['kyc_doc_type_id'] ?? 0); + $storedName = trim((string) ($upload['file_name'] ?? '')); + if ($typeId <= 0 || $storedName === '' || isset($latestByType[$typeId])) { + continue; + } + $latestByType[$typeId] = $upload; + } - return $table; - // print_r($table); die; + foreach ($docs as &$doc) { + $typeId = (int) ($doc['id'] ?? 0); + $latest = $latestByType[$typeId] ?? null; + $doc['upload_doc_name'] = $latest['file_name'] ?? null; + $doc['client_kyc_id'] = $latest['id'] ?? null; + } + unset($doc); + $result['data'] = $docs; + $result['client_id'] = $clientId; + + return view('client_kyc_primary_table', $result); } public function generateKycOthersTable($client_id) @@ -4905,17 +4945,139 @@ class ClientController extends AdminController return $this->respond(['Status' => true, 'code' => 200, 'data' => $data, 'client_policy_id' => $client_policy_id, 'policy' => $polices], 200); } - public function downloadKYCDocument($file_name) + public function downloadKYCDocument($fileOrId = null) { + return $this->downloadKycDocumentFromStorage($fileOrId); + } - $file = WRITEPATH . 'uploads/client_kyc_documents/' . $file_name; // Example file path - - if (file_exists($file)) { - return $this->response->download($file, null)->setFileName($file_name); - } else { - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + /** + * Download KYC file by client_kyc_documents.id (preferred) or legacy stored file_name. + */ + private function downloadKycDocumentFromStorage($fileOrId) + { + if ($fileOrId === null || $fileOrId === '') { + $data['message'] = 'No file specified.'; + return view('errors/404', $data); } + + $uploadFilePath = WRITEPATH . 'uploads/client_kyc_documents'; + $storedFileName = null; + $downloadAs = null; + + // Prefer lookup by KYC document id so duplicate original names never collide. + if (ctype_digit((string) $fileOrId)) { + $record = $this->clientKYCDocsModel->find((int) $fileOrId); + if (empty($record) || empty($record['file_name'])) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } + $storedFileName = basename((string) $record['file_name']); + $downloadAs = storage_upload_display_name($storedFileName); + } else { + $storedFileName = basename((string) $fileOrId); + $downloadAs = storage_upload_display_name($storedFileName); + } + + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadFilePath, $storedFileName); + + if (!($result['success'] ?? false)) { + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); + } + + // Local disk: stream file path + if (!empty($result['path']) && is_file($result['path'])) { + return $this->response->download($result['path'], null)->setFileName($downloadAs); + } + + // S3: force attachment download in the same flow (no new-tab redirect) + if (!empty($result['content'])) { + return $this->response + ->download($downloadAs, $result['content']) + ->setFileName($downloadAs); + } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); + } + + /** + * Soft-delete helper: deactivate row and remove unused physical/S3 object. + */ + private function softDeleteKycDocument(int $id): bool + { + $record = $this->clientKYCDocsModel->find($id); + if (empty($record)) { + return false; + } + + $updated = $this->clientKYCDocsModel->update($id, [ + 'is_active' => 0, + 'updated_by' => get_session_userid(), + ]); + + if (! $updated) { + return false; + } + + $storedFileName = basename((string) ($record['file_name'] ?? '')); + if ($storedFileName === '') { + return true; + } + + // Only delete storage object when no other active row still points at it (legacy shared names). + $stillUsed = $this->clientKYCDocsModel + ->where('file_name', $storedFileName) + ->where('is_active', 1) + ->where('id !=', $id) + ->countAllResults(); + + if ($stillUsed === 0) { + $storage = \Config\Services::getFileStorageService(); + $storage->delete(WRITEPATH . 'uploads/client_kyc_documents', $storedFileName); + } + + return true; + } + + /** + * Soft-delete every active primary KYC upload for a client + doc type. + * Prevents duplicate Status rows for the same required document. + */ + private function deactivateActivePrimaryKycDocs(int $clientId, int $kycDocTypeId): void + { + if ($clientId <= 0 || $kycDocTypeId <= 0) { + return; + } + + $existing = $this->clientKYCDocsModel + ->where('client_id', $clientId) + ->where('kyc_doc_type_id', $kycDocTypeId) + ->where('is_active', 1) + ->findAll(); + + foreach ($existing as $row) { + $this->softDeleteKycDocument((int) $row['id']); + } + } + + /** + * Insert a primary KYC file after deactivating any previous active upload + * for the same client + document type. + * + * @return int|false insert id + */ + private function insertPrimaryKycDocument(array $data) + { + $clientId = (int) ($data['client_id'] ?? 0); + $kycDocTypeId = (int) ($data['kyc_doc_type_id'] ?? 0); + + if ($clientId > 0 && $kycDocTypeId > 0) { + $this->deactivateActivePrimaryKycDocs($clientId, $kycDocTypeId); + } + + return $this->clientKYCDocsModel->insert($data); } //for this function policy binding dropdown list use ( client policy ) @@ -9926,22 +10088,9 @@ class ClientController extends AdminController return $default_templates_inserted ? true : false; } - public function downloadClientKycDocs_2($file_name = null) + public function downloadClientKycDocs_2($fileOrId = null) { - if (!$file_name) { - return "No file specified."; - } - - // Use absolute path to your upload folder - $file = WRITEPATH . 'uploads/client_kyc_documents/' . $file_name; - - if (file_exists($file)) { - // download() takes the path as first param and null (or data) as second - return $this->response->download($file, null); - } else { - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); - } + return $this->downloadKycDocumentFromStorage($fileOrId); } } diff --git a/app/Controllers/EmpDataServiceController.php b/app/Controllers/EmpDataServiceController.php index 62f10af2..783990a4 100755 --- a/app/Controllers/EmpDataServiceController.php +++ b/app/Controllers/EmpDataServiceController.php @@ -1488,7 +1488,7 @@ class EmpDataServiceController extends BaseController 'event_type' => $file['event_type'], ]; - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); $excel_data = $this->readExcelFileToArray($file_name_with_path); $excel_header = $excel_data[0]; unset($excel_data[0]); @@ -2016,7 +2016,7 @@ class EmpDataServiceController extends BaseController $this->myLogger->logme('error', 'Inception Update TPA and UHID -- file name : {data}', ['data' => $file['file_name']]); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); $excel_data = $this->readExcelFileToArray($file_name_with_path); unset($excel_data[0]); // Remove header row // array_pop($excel_data); @@ -2286,9 +2286,9 @@ class EmpDataServiceController extends BaseController 'event_type' => $file['event_type'], ]; - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { $this->myLogger->logme('error', 'Correction File Validation -- The Physical file not found'); $this->myLogger->logme('error', 'Correction File Validation -- File Name : {data}', ['data' => $file['file_name']]); @@ -2574,7 +2574,7 @@ class EmpDataServiceController extends BaseController $this->myLogger->logme('error', 'importCorrectionUpdateEndorsementID file name : {data}', ['data'=> $file['file_name']]); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); $excel_data = $this->readExcelFileToArray($file_name_with_path); unset($excel_data[0]); @@ -2711,9 +2711,9 @@ class EmpDataServiceController extends BaseController $client_policy_data = $this->clientPolicyModel->where('id', $client_policy_id)->first(); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { return 'The Physical file not found'; } @@ -3213,7 +3213,7 @@ class EmpDataServiceController extends BaseController $this->myLogger->logme('error', 'SI Enhancement Update Endorsement ID -- file name : {data}', ['data' => $file['file_name']]); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); $excel_data = $this->readExcelFileToArray($file_name_with_path); unset($excel_data[0]); @@ -3455,9 +3455,9 @@ class EmpDataServiceController extends BaseController ]; - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { return 'The Physical file not found'; } @@ -3788,7 +3788,7 @@ class EmpDataServiceController extends BaseController $this->myLogger->logme('error', 'Deletion Update Endorsement ID -- file name : {data}', ['data' => $file['file_name']]); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); $excel_data = $this->readExcelFileToArray($file_name_with_path); unset($excel_data[0]); // array_pop($excel_data); @@ -3968,9 +3968,9 @@ class EmpDataServiceController extends BaseController $batch_code = $file['batch_code']; $user_id = $file['created_by']; - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { $this->myLogger->logme('error', 'Addition And Dependent Addition File Validation -- The Physical file not found'); $this->myLogger->logme('error', 'Addition And Dependent Addition File Validation -- File Name : {data}', ['data' => $file['file_name']]); @@ -4273,7 +4273,7 @@ class EmpDataServiceController extends BaseController $this->myLogger->logme('error', 'importCorrectionUpdateEndorsementID file name : {data}', ['data'=> $file['file_name']]); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); $excel_data = $this->readExcelFileToArray($file_name_with_path); unset($excel_data[0]); diff --git a/app/Controllers/EmployeeController.php b/app/Controllers/EmployeeController.php index d209ca84..6218dc44 100755 --- a/app/Controllers/EmployeeController.php +++ b/app/Controllers/EmployeeController.php @@ -288,12 +288,10 @@ class EmployeeController extends AdminController } } - $is_moved = $avatar->move(WRITEPATH . 'uploads/excel/'); - if ($is_moved) { - $filename = $avatar->getName(); - $fileSize = $avatar->getSize(); // File size in bytes - $fileSize = $fileSize / (1024 * 1024); // Convert to MB - // Handle successful upload, e.g., log success or further processing + $fileSizeBytes = (is_object($avatar) && method_exists($avatar, 'getSize')) ? (int) $avatar->getSize() : 0; + $filename = storage_file_Upload($avatar, WRITEPATH . 'uploads/excel/', UPLOAD_EXT_EXCEL); + if ($filename !== '') { + $fileSize = $fileSizeBytes / (1024 * 1024); // Convert to MB $this->myLogger->logme("error", 'File move successful'); } else { $this->myLogger->logme("error", 'File move failed'); @@ -873,8 +871,11 @@ class EmployeeController extends AdminController $batch_data['file'] = $this->request->getFile('import_file_data'); $file = $this->request->getFile('import_file_data'); - $is_moved = $file->move(WRITEPATH . 'uploads/import_excel'); - $filename = $file->getName(); + $filename = storage_file_Upload($file, WRITEPATH . 'uploads/import_excel/', UPLOAD_EXT_EXCEL); + if ($filename === '') { + session()->setFlashdata('error', 'File upload failed'); + return redirect()->to(base_url('employee/upload')); + } $this->myLogger->logme('error', 'Inception Import file name : {data}', ['data' => $filename]); $random_number_count = 4; @@ -1182,34 +1183,32 @@ class EmployeeController extends AdminController public function downloadFileList($file_id = null) { - // $actionType = $this->request->getGet(); $file_data = $this->fileModel->where('id', $file_id)->first(); - $fileName = $file_data['file_name']; + if (empty($file_data) || empty($file_data['file_name'])) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } - $error_data = json_decode($file_data['reason']); - - $filePath = WRITEPATH . '/uploads/excel/' . $fileName; + $fileName = basename((string) $file_data['file_name']); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download(WRITEPATH . 'uploads/excel', $fileName); try { - - // Check if the file exists - if (file_exists($filePath)) { - // Set the appropriate MIME type - $mimeType = mime_content_type($filePath); - - // Send the file to the client for download - return $this->response->download($filePath, null, $mimeType); - } else { - - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + if ($result['success'] ?? false) { + $downloadAs = storage_upload_display_name($fileName); + 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); + } } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } catch (\Exception $e) { - // Handle any exceptions - $errorMessage = $e->getMessage(); - $this->myLogger->logme('error', $errorMessage); - // You can return an error response here - echo $errorMessage; + $this->myLogger->logme('error', $e->getMessage()); + echo $e->getMessage(); } } @@ -1253,10 +1252,10 @@ class EmployeeController extends AdminController ], 200); } - $filePath = WRITEPATH . 'uploads/excel/' . $file_name['file_name']; + $filePath = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file_name['file_name']); // ✅ File not exists on disk - if (!file_exists($filePath)) { + if (empty($filePath) || !file_exists($filePath)) { return $this->respond([ 'dataStatus' => false, 'code' => 200, @@ -1349,10 +1348,10 @@ class EmployeeController extends AdminController } $fileName = $file_data['file_name']; - $filePath = WRITEPATH . '/uploads/excel/' . $fileName; + $filePath = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $fileName); // Check if the file exists - if (!file_exists($filePath)) { + if (empty($filePath) || !file_exists($filePath)) { $error_message = "File not found"; $this->myLogger->logme('error', $error_message . ' for file id ' . $file_id); $data['message'] = 'Physical File Not Found'; @@ -2513,7 +2512,7 @@ class EmployeeController extends AdminController // dd($error_data); - $file_name_with_path = WRITEPATH . "/uploads/import_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/import_excel', $file['file_name']); if (!file_exists($file_name_with_path)) { $error_message = "File not found"; @@ -3036,32 +3035,32 @@ class EmployeeController extends AdminController public function download_import_file($file_id) { - // $actionType = $this->request->getGet(); $file_data = $this->batchFileModel->where('id', $file_id)->first(); - $fileName = $file_data['file_name']; + if (empty($file_data) || empty($file_data['file_name'])) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } - $filePath = WRITEPATH . '/uploads/import_excel/' . $fileName; + $fileName = basename((string) $file_data['file_name']); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download(WRITEPATH . 'uploads/import_excel', $fileName); try { - - // Check if the file exists - if (file_exists($filePath)) { - // Set the appropriate MIME type - $mimeType = mime_content_type($filePath); - - // Send the file to the client for download - return $this->response->download($filePath, null, $mimeType); - } else { - - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + if ($result['success'] ?? false) { + $downloadAs = storage_upload_display_name($fileName); + 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); + } } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } catch (\Exception $e) { - // Handle any exceptions - $errorMessage = $e->getMessage(); - $this->myLogger->logme('error', $errorMessage); - // You can return an error response here - echo $errorMessage; + $this->myLogger->logme('error', $e->getMessage()); + echo $e->getMessage(); } } @@ -4835,9 +4834,13 @@ class EmployeeController extends AdminController date('Ymd_His') ); $filePath = WRITEPATH . 'uploads/excel/' . $fileName; + if (! is_dir(WRITEPATH . 'uploads/excel')) { + mkdir(WRITEPATH . 'uploads/excel', 0755, true); + } $writer = new Xlsx($spreadsheet); $writer->save($filePath); + storage_mirror_generated_file($filePath, WRITEPATH . 'uploads/excel', $fileName); // Create a new entry in the files table so that the // existing Employee Upload with Events pipeline can process it. @@ -5118,9 +5121,13 @@ class EmployeeController extends AdminController date('Ymd_His') ); $filePath = WRITEPATH . 'uploads/excel/' . $fileName; + if (! is_dir(WRITEPATH . 'uploads/excel')) { + mkdir(WRITEPATH . 'uploads/excel', 0755, true); + } $writer = new Xlsx($spreadsheet); $writer->save($filePath); + storage_mirror_generated_file($filePath, WRITEPATH . 'uploads/excel', $fileName); // Insert into files table so the existing correction pipeline can process it. $loggedInUserId = $batchFile['created_by'] ?? get_session_userid(); @@ -7109,9 +7116,13 @@ class EmployeeController extends AdminController date('Ymd_His') ); $generatedExcelPath = WRITEPATH . 'uploads/excel/' . $generatedFileName; + if (! is_dir(WRITEPATH . 'uploads/excel')) { + mkdir(WRITEPATH . 'uploads/excel', 0755, true); + } $writer = new Xls($spreadsheet); $writer->save($generatedExcelPath); + storage_mirror_generated_file($generatedExcelPath, WRITEPATH . 'uploads/excel', $generatedFileName); // return "HI"; // 1) Create files-table entry for EmployeeServiceController::employeeDisembark. $newFileId = $this->fileModel->insert([ @@ -7198,7 +7209,13 @@ class EmployeeController extends AdminController date('Ymd_His') ); $generatedImportPath = WRITEPATH . 'uploads/import_excel/' . $generatedImportFileName; + if (! is_dir(WRITEPATH . 'uploads/import_excel')) { + mkdir(WRITEPATH . 'uploads/import_excel', 0755, true); + } $success = generate_excel($excel_data_info['header'], $excel_data_info['data'], $generatedImportPath); + if ($success && is_file($generatedImportPath)) { + storage_mirror_generated_file($generatedImportPath, WRITEPATH . 'uploads/import_excel', $generatedImportFileName); + } // print_rr($success); // die; diff --git a/app/Controllers/EmployeeMultiEventServiceController.php b/app/Controllers/EmployeeMultiEventServiceController.php index fef1e2f3..589e9efe 100644 --- a/app/Controllers/EmployeeMultiEventServiceController.php +++ b/app/Controllers/EmployeeMultiEventServiceController.php @@ -1026,10 +1026,10 @@ class EmployeeMultiEventServiceController extends BaseController //file not found in DB return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physcial file not found - " . $file_name_with_path; // echo $message; @@ -1399,10 +1399,10 @@ class EmployeeMultiEventServiceController extends BaseController return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physcial file not found - " . $file_name_with_path; $this->myLogger->logme('error', ($message . ' for file id ' . $file_id)); @@ -1829,10 +1829,10 @@ class EmployeeMultiEventServiceController extends BaseController return array('status' => false, 'msg' => 'file not found in DB'); } - // $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + // $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); // //check physical file - // if (!file_exists($file_name_with_path)) { + // if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { // //file not found update status and reason // $message = "Physical file not found"; // // echo $message; @@ -1975,7 +1975,7 @@ class EmployeeMultiEventServiceController extends BaseController $file['action'] = $params['action']; // dd($file); - // $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + // $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); // $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); // $sheet = $spreadsheet->getActiveSheet(); // $highestRowAndColumn = $sheet->getHighestRowAndColumn(); @@ -2114,7 +2114,7 @@ class EmployeeMultiEventServiceController extends BaseController $file['action'] = $params['action']; - // $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + // $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); // $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); // $sheet = $spreadsheet->getActiveSheet(); // $highestRowAndColumn = $sheet->getHighestRowAndColumn(); @@ -2191,7 +2191,7 @@ class EmployeeMultiEventServiceController extends BaseController $file = $this->fileModel->find((int)$file_id); $file['action'] = $params['action']; - // $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + // $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); // $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); // $sheet = $spreadsheet->getActiveSheet(); // $highestRowAndColumn = $sheet->getHighestRowAndColumn(); @@ -2492,10 +2492,10 @@ class EmployeeMultiEventServiceController extends BaseController // dd($error_data); // return $error_data; - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check the file exist or not - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { $error_message = "File not found"; $this->myLogger->logme('error', ($error_message . ' for file id ' . $file_id)); return 0; @@ -2656,10 +2656,10 @@ class EmployeeMultiEventServiceController extends BaseController //file not found in DB return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physical file not found"; // echo $message; @@ -2870,7 +2870,7 @@ class EmployeeMultiEventServiceController extends BaseController $deletion_column_to_check = $this->deletion_excel_columns; $si_column_to_check = $this->si_enhance_excel_columns; - $file_name_with_path = WRITEPATH . "uploads/excel/" . $file_name; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file_name); $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $worksheet = $spreadsheet->getActiveSheet(); @@ -3103,8 +3103,8 @@ class EmployeeMultiEventServiceController extends BaseController } // ✅ Define File Paths - // $inception_file_path = !empty($file['file_name']) ? WRITEPATH . "uploads/excel/" . $file['file_name'] : ''; - $member_file_path = !empty($lead_data['file_name']) ? WRITEPATH . "uploads/lead_files/" . $lead_data['file_name'] : ''; + // $inception_file_path = !empty($file['file_name']) ? storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']) : ''; + $member_file_path = !empty($lead_data['file_name']) ? (storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']) ?? '') : ''; // ✅ Check Inception File // if (empty($file['file_name']) || !file_exists($inception_file_path)) { @@ -3118,7 +3118,7 @@ class EmployeeMultiEventServiceController extends BaseController // } // ✅ Check Member File - if (empty($lead_data['file_name']) || !file_exists($member_file_path)) { + if (empty($lead_data['file_name']) || empty($member_file_path) || !file_exists($member_file_path)) { $message = empty($lead_data['file_name']) ? "Member file name is missing" : "Member data physical file not found"; $this->myLogger->logme('error', "$message for lead id {$lead_data['id']}"); $this->fileModel->update($file_id, [ diff --git a/app/Controllers/EmployeeRestController.php b/app/Controllers/EmployeeRestController.php index bc2fc7b3..b1fd0e42 100755 --- a/app/Controllers/EmployeeRestController.php +++ b/app/Controllers/EmployeeRestController.php @@ -757,12 +757,13 @@ class EmployeeRestController extends AdminController $sum_insured_amount_for_check_employee_1 = isset($policy_permium_1['si']) ? $policy_permium_1['si'] : null; $sum_insured_amount_for_check_employee_2 = isset($policy_permium_2['si']) ? $policy_permium_2['si'] : null; - $fileName = sanitize_upload_filename($file->getName()); - $is_moved = $file->move(WRITEPATH . 'uploads/excel', $fileName); - $filename = $file->getName(); - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $filename; + $filename = storage_file_Upload($file, WRITEPATH . 'uploads/excel/', UPLOAD_EXT_EXCEL); + if ($filename === '') { + return $this->respond(['status' => 'failed', 'code' => 500, 'message' => 'File upload failed.'], 200); + } + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $filename); - //make an entry in DB + //make an entry in DB $file_id = $this->fileModel->insert(['file_name' => $filename, 'client_id' => $client_id, 'policy_id' => $policy_id, 'created_by' => $employee_id, 'status' => 'inprogress', 'action' => 'enrollment', 'client_branch_id' => $client_branch_id]); //here field policy_id have client_policy_id and not policy id from policy master $this->myLogger->logme("error", '{file_id} - client uploaded success', ['file_id' => $file_id]); @@ -775,7 +776,7 @@ class EmployeeRestController extends AdminController } //check the file exist or not - if (! file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || ! file_exists($file_name_with_path)) { session()->setFlashdata('error', 'File not found'); return redirect()->to(base_url('employee/upload')); } @@ -3850,7 +3851,7 @@ class EmployeeRestController extends AdminController $file_data = []; if (isset($get_file_data) && ! empty($get_file_data)) { $file_path = WRITEPATH . 'uploads/claim_files/'; - $file_data = multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); + $file_data = storage_multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); } if (empty($received_data['doa'])) { @@ -4808,20 +4809,17 @@ class EmployeeRestController extends AdminController ]; } - // Upload folder path + // Upload folder path (S3-only when FILE_STORAGE_DRIVER=s3) $uploadPath = WRITEPATH . 'uploads/hr_files/'; - - // If directory not exists, create it - if (! is_dir($uploadPath)) { - mkdir($uploadPath, 0755, true); + $newFileName = storage_file_Upload($file, $uploadPath, UPLOAD_EXT_EXCEL); + if ($newFileName === '') { + return [ + 'status' => false, + 'message' => 'File upload failed.', + 'data' => [], + ]; } - // New file name with timestamp - $newFileName = time() . '_' . $file->getRandomName(); - - // Move file - $file->move($uploadPath, $newFileName); - // Prepare data $data = [ 'client_id' => $post_data['client_id'], @@ -4966,25 +4964,36 @@ class EmployeeRestController extends AdminController // Find record if ($client_data && $client_data['hr_file_processed_by'] == 1) { $record = $this->fileModel->where('id', (int) $file_id)->find(); - $uploadPath = WRITEPATH . 'uploads/excel/'; + $uploadPath = WRITEPATH . 'uploads/excel'; } else { $record = $this->hrFileUploadModel->where('id', (int) $file_id)->find(); - $uploadPath = WRITEPATH . 'uploads/hr_files/'; + $uploadPath = WRITEPATH . 'uploads/hr_files'; } if (! $record) { return $this->failNotFound("File record not found"); } - $filePath = $uploadPath . $record[0]['file_name']; - - if (! file_exists($filePath)) { + $storedName = basename((string) ($record[0]['file_name'] ?? '')); + if ($storedName === '') { return $this->failNotFound("File not found on server"); } - // Force file download - return $this->response->download($filePath, null) - ->setFileName($record[0]['file_name']); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadPath, $storedName); + if (! ($result['success'] ?? false)) { + return $this->failNotFound("File not found on server"); + } + + $downloadAs = storage_upload_display_name($storedName); + 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); + } + + return $this->failNotFound("File not found on server"); } catch (\Exception $e) { return $this->failServerError($e->getMessage()); @@ -5626,7 +5635,7 @@ class EmployeeRestController extends AdminController $file_data = []; if (isset($get_file_data) && ! empty($get_file_data)) { $file_path = WRITEPATH . 'uploads/claim_files/'; - $file_data = multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); + $file_data = storage_multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); } $result = $this->handleCliamFiles($file_data, $ticket_id, null, false, true); @@ -5671,17 +5680,26 @@ class EmployeeRestController extends AdminController return view('errors/404', $data); } - $uploadPath = WRITEPATH . 'uploads/import_excel/'; - $filePath = $uploadPath . $record['file_name']; + $uploadPath = WRITEPATH . 'uploads/import_excel'; + $fileName = basename((string) ($record['file_name'] ?? '')); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadPath, $fileName); - if (! file_exists($filePath)) { - // return $this->failNotFound("File not found on server"); + if (! ($result['success'] ?? false)) { $data['message'] = 'The Physical File Not Found'; return view('errors/404', $data); } - // Force file download - return $this->response->download($filePath, null)->setFileName($record['file_name']); + $downloadAs = storage_upload_display_name($fileName); + 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); + } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } catch (\Exception $e) { $data['message'] = 'File record not found'; return view('errors/404', $data); @@ -5880,14 +5898,31 @@ class EmployeeRestController extends AdminController return $this->respond(['status' => false, 'code' => 404, 'message' => 'The Physical File Not Found in server'], 200); } - $filePath = $uploadFilePath . '/' . $pt_files_data['file_name']; - - if (! file_exists($filePath)) { + $storedFileName = basename((string) ($pt_files_data['file_name'] ?? '')); + if ($storedFileName === '') { return $this->respond(['status' => false, 'code' => 404, 'message' => 'The Physical File Not Found in server'], 200); } - // Force file download - return $this->response->download($filePath, null)->setFileName($pt_files_data['file_name']); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadFilePath, $storedFileName); + + if (! ($result['success'] ?? false)) { + return $this->respond(['status' => false, 'code' => 404, 'message' => 'The Physical File Not Found in server'], 200); + } + + $downloadAs = storage_upload_display_name($storedFileName); + + 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); + } + + return $this->respond(['status' => false, 'code' => 404, 'message' => 'The Physical File Not Found in server'], 200); } public function bulkEcardDownloadAsZip() diff --git a/app/Controllers/EmployeeServiceController.php b/app/Controllers/EmployeeServiceController.php index 6db0be9d..0179280a 100755 --- a/app/Controllers/EmployeeServiceController.php +++ b/app/Controllers/EmployeeServiceController.php @@ -803,10 +803,10 @@ class EmployeeServiceController extends AdminController //file not found in DB return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if(!file_exists($file_name_with_path)) + if(empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physcial file not found"; @@ -1123,10 +1123,10 @@ class EmployeeServiceController extends AdminController //file not found in DB return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if(!file_exists($file_name_with_path)) + if(empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physical file not found"; @@ -1382,10 +1382,10 @@ class EmployeeServiceController extends AdminController //file not found in DB return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if (!file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physical file not found"; // echo $message; @@ -1575,7 +1575,7 @@ class EmployeeServiceController extends AdminController $file = $this->fileModel->find((int)$file_id); // dd($file); - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $sheet = $spreadsheet->getActiveSheet(); @@ -1715,7 +1715,7 @@ class EmployeeServiceController extends AdminController $file = $this->fileModel->find((int)$file_id); // dd($file); - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $sheet = $spreadsheet->getActiveSheet(); @@ -1802,7 +1802,7 @@ class EmployeeServiceController extends AdminController $file = $this->fileModel->find((int)$file_id); // dd($file); - $file_name_with_path = WRITEPATH . "/uploads/excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $sheet = $spreadsheet->getActiveSheet(); @@ -2137,10 +2137,10 @@ class EmployeeServiceController extends AdminController // dd($error_data); // return $error_data; - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check the file exist or not - if(!file_exists($file_name_with_path)) + if(empty($file_name_with_path) || !file_exists($file_name_with_path)) { $error_message = "File not found"; $this->myLogger->logme('error',($error_message . ' for file id ' . $file_id)); @@ -2299,10 +2299,10 @@ class EmployeeServiceController extends AdminController //file not found in DB return array('status' => false, 'msg' => 'file not found in DB'); } - $file_name_with_path = WRITEPATH."/uploads/excel/".$file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']); //check physical file - if(!file_exists($file_name_with_path)) + if(empty($file_name_with_path) || !file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physical file not found"; @@ -2539,8 +2539,8 @@ class EmployeeServiceController extends AdminController } // ✅ Define File Paths - $inception_file_path = !empty($file['file_name']) ? WRITEPATH . "uploads/excel/" . $file['file_name'] : ''; - $member_file_path = !empty($lead_data['file_name']) ? WRITEPATH . "uploads/lead_files/" . $lead_data['file_name'] : ''; + $inception_file_path = !empty($file['file_name']) ? (storage_ensure_local_file(WRITEPATH . 'uploads/excel', $file['file_name']) ?? '') : ''; + $member_file_path = !empty($lead_data['file_name']) ? (storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']) ?? '') : ''; // ✅ Check Inception File if (empty($file['file_name']) || !file_exists($inception_file_path)) { @@ -2554,7 +2554,7 @@ class EmployeeServiceController extends AdminController } // ✅ Check Member File - if (empty($lead_data['file_name']) || !file_exists($member_file_path)) { + if (empty($lead_data['file_name']) || empty($member_file_path) || !file_exists($member_file_path)) { $message = empty($lead_data['file_name']) ? "Member file name is missing" : "Member data physical file not found"; $this->myLogger->logme('error', "$message for lead id {$lead_data['id']}"); $this->fileModel->update($file_id, [ diff --git a/app/Controllers/FhplApiController.php b/app/Controllers/FhplApiController.php index 16bc30ed..f736d7a2 100644 --- a/app/Controllers/FhplApiController.php +++ b/app/Controllers/FhplApiController.php @@ -70,7 +70,7 @@ class FhplApiController extends BaseController public function SubmitClaim($claimId = null) // 515 { - helper(['api', 'tpa_claim_push_log']); + helper(['api', 'tpa_claim_push_log', 'utility']); $data = $this->db->table('ticket_master tm') ->select(' @@ -103,18 +103,21 @@ class FhplApiController extends BaseController return ['status' => false, 'message' => 'Claim Push FAILED | Claim or File Missing']; } - // Build absolute file path $file_id = $data['fileId'] ?? null; $filename = basename($data['filePath']); - $pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename; - - if (!file_exists($pdfPath)) { - tpa_claim_push_log($claimId, "FHPL - Claim Push FAILED | claimId: {$claimId} - PDF not found on server"); + $resolved = storage_resolve_claim_file_path(WRITEPATH . 'uploads/claim_files', $filename); + if (!($resolved['success'] ?? false) || empty($resolved['path'])) { + tpa_claim_push_log($claimId, "FHPL - Claim Push FAILED | claimId: {$claimId} - PDF not found in local/S3"); return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server']; } - // Convert PDF to Base64 - $fileContent = base64_encode(file_get_contents($pdfPath)); + $rawContent = @file_get_contents($resolved['path']); + storage_cleanup_temp_claim_file($resolved); + if ($rawContent === false) { + tpa_claim_push_log($claimId, "FHPL - Claim Push FAILED | claimId: {$claimId} - Unable to read file for base64"); + return ['status' => false, 'message' => 'Claim Push FAILED | PDF not readable']; + } + $fileContent = base64_encode($rawContent); // Generate FHPL Token $tokenResponse = $this->generateAuthToken(); diff --git a/app/Controllers/HealthIndiaApiController.php b/app/Controllers/HealthIndiaApiController.php index 485e81d9..1050ceb3 100644 --- a/app/Controllers/HealthIndiaApiController.php +++ b/app/Controllers/HealthIndiaApiController.php @@ -84,7 +84,7 @@ class HealthIndiaApiController extends BaseController public function SubmitClaim($claimId = null) { - helper(['api', 'tpa_claim_push_log']); + helper(['api', 'tpa_claim_push_log', 'utility']); tpa_claim_push_log($claimId, 'HEALTH_INDIA - Claim Push | Started for claimId: ' . $claimId); @@ -130,18 +130,21 @@ class HealthIndiaApiController extends BaseController return ['status' => false, 'message' => 'Claim Push FAILED | File Missing']; } - // Build absolute file path $file_id = $data['fileId'] ?? null; $filename = basename($data['filePath']); - $pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename; - - if (!file_exists($pdfPath)) { - tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - PDF not found on server at path: {$pdfPath}"); + $resolved = storage_resolve_claim_file_path(WRITEPATH . 'uploads/claim_files', $filename); + if (!($resolved['success'] ?? false) || empty($resolved['path'])) { + tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - PDF not found in local/S3 for file: {$filename}"); return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server']; } - // Convert PDF to Base64 - $fileContent = base64_encode(file_get_contents($pdfPath)); + $rawContent = @file_get_contents($resolved['path']); + storage_cleanup_temp_claim_file($resolved); + if ($rawContent === false) { + tpa_claim_push_log($claimId, "HEALTH_INDIA - Claim Push FAILED | claimId: {$claimId} - Unable to read file for base64: {$filename}"); + return ['status' => false, 'message' => 'Claim Push FAILED | PDF not readable']; + } + $fileContent = base64_encode($rawContent); // Generate Health India Token $tokenResponse = $this->generateAuthToken(); diff --git a/app/Controllers/LeadsController.php b/app/Controllers/LeadsController.php index 231ce0e1..e83c0ea2 100644 --- a/app/Controllers/LeadsController.php +++ b/app/Controllers/LeadsController.php @@ -1172,7 +1172,7 @@ class LeadsController extends BaseController $multi_file_data = []; foreach ($files as $index => $value) { - $file_name = file_Upload_for_lead($value, $uploadFilePath, UPLOAD_EXT_LEAD_FILES); + $file_name = storage_file_Upload($value, $uploadFilePath, UPLOAD_EXT_LEAD_FILES); $multi_file_data[] = [ 'file_name' => $file_name, 'docs_name' => $docs_names[$index] ?? '', @@ -1280,7 +1280,7 @@ class LeadsController extends BaseController } $uploadFilePath = WRITEPATH . 'uploads/lead_files/'; - $fileName = file_Upload_for_lead($file, $uploadFilePath, UPLOAD_EXT_LEAD_FILES); + $fileName = storage_file_Upload($file, $uploadFilePath, UPLOAD_EXT_LEAD_FILES); if (empty($fileName)) { return $this->respond(['status' => 'error', 'code' => 400, 'message' => 'File upload failed. Check file type or size.'], 400); @@ -2170,13 +2170,15 @@ class LeadsController extends BaseController continue; } + $utrNo = isset($installment['utr_no']) ? trim((string) $installment['utr_no']) : ''; + $data = [ 'lead_id' => $lead_id, 'installment_amount' => $installment['installment_amount'] ?? null, 'payment_date' => ! empty($installment['payment_date']) ? change_date_format($installment['payment_date']) : null, - 'utr_no' => $installment['utr_no'] ?? null, + 'utr_no' => $utrNo !== '' ? $utrNo : null, ]; $installmentId = ! empty($installment['id']) ? (int) $installment['id'] : null; @@ -2247,11 +2249,11 @@ class LeadsController extends BaseController $temp_file_path = $filepath['filePath']; $temp_file_name = $filepath['fileName']; - $lead_file_path = WRITEPATH . 'uploads/lead_files/' . $lead_data['file_name']; + $lead_file_path = storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']); // dd($lead_data, $temp_file_path, $temp_file_name, $lead_file_path); - if ($lead_file_path) { + if (! empty($lead_file_path) && is_file($lead_file_path)) { $filePaths = [ ['file_path' => $temp_file_path, 'sheets' => []], ['file_path' => $lead_file_path, 'sheets' => []], @@ -3058,22 +3060,23 @@ class LeadsController extends BaseController { $lead_id = $params['lead_id']; $lead_data = $this->leadsModel->find((int) $lead_id); - $file_name_with_path = WRITEPATH . "/uploads/lead_files/" . $lead_data['file_name']; if (! $lead_data) { return ['status' => 'failed', 'message' => 'Opportunity data not found']; } + $file_name_with_path = ! empty($lead_data['file_name']) + ? storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']) + : null; + try { if ($lead_data['file_name']) { - // $file_name_with_path = WRITEPATH . "/uploads/lead_files/NonPrintableCharacters.xlsx"; - //check physical file - if (! file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || ! file_exists($file_name_with_path)) { //file not found update status and reason $message = "Lead Physcial file not found"; // echo $message; - $this->myLogger->logme('error', ($message . ' for file ' . $file_name_with_path)); + $this->myLogger->logme('error', ($message . ' for file ' . ($lead_data['file_name'] ?? ''))); return ['status' => 'failed', 'message' => 'no physical file']; } @@ -3129,12 +3132,13 @@ class LeadsController extends BaseController echo "Location: " . $result['fullpath'] . "\n"; echo "Filename: " . $result['filename'] . "\n"; $filePaths = [ - ['file_path' => WRITEPATH . 'uploads/lead_files/' . $lead_data['file_name'], 'sheets' => [0, 1]], - ['file_path' => WRITEPATH . '/uploads/lead_files/' . $result['filename'], 'sheets' => []], + ['file_path' => $file_name_with_path, 'sheets' => [0, 1]], + ['file_path' => $result['fullpath'], 'sheets' => []], ]; - $outputPath = WRITEPATH . 'uploads/lead_files/' . $lead_data['file_name']; + $outputPath = $file_name_with_path; $result_merge = ExcelMergeHelper::mergeExcelFiles($filePaths, $outputPath); if ($result_merge) { + storage_mirror_generated_file($outputPath, WRITEPATH . 'uploads/lead_files', $lead_data['file_name']); // Call the delete function after the file is successfully created $deleteResponse = $this->deleteGeneratedFile($result['fullpath']); @@ -3697,8 +3701,8 @@ class LeadsController extends BaseController if ($lead_data['file_name'] != null && $lead_data['file_name'] != '') { $temp_file_path = $file_info['filePath']; $temp_file_name = $file_info['fileName']; - $lead_file_path = WRITEPATH . 'uploads/lead_files/' . $lead_data['file_name']; - if ($lead_file_path) { + $lead_file_path = storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']); + if (! empty($lead_file_path) && is_file($lead_file_path)) { $filePaths = [ ['file_path' => $temp_file_path, 'sheets' => []], ['file_path' => $lead_file_path, 'sheets' => []], @@ -7406,7 +7410,7 @@ class LeadsController extends BaseController $fileIds = json_decode($json_string, true); log_message('error', 'Decoded fileIds: ' . print_r($fileIds, true)); - $lead_file_path = WRITEPATH . 'uploads/lead_files/'; + $lead_file_path = WRITEPATH . 'uploads/lead_files'; log_message('error', 'Lead file path: ' . $lead_file_path); foreach ($fileIds as $id) { @@ -7422,24 +7426,24 @@ class LeadsController extends BaseController if ($lead_file) { log_message('error', 'Found lead file: ' . print_r($lead_file, true)); - $fullPath = $lead_file_path . $lead_file['file_name']; - log_message('error', 'Full file path: ' . $fullPath); + if (empty($lead_file['file_name'])) { + log_message('error', 'File name is empty for ID: ' . $id); + continue; + } - if (file_exists($fullPath)) { + $fullPath = storage_ensure_local_file($lead_file_path, $lead_file['file_name']); + log_message('error', 'Full file path: ' . ($fullPath ?? '')); + + if (! empty($fullPath) && file_exists($fullPath)) { log_message('error', 'File exists at path: ' . $fullPath); + log_message('error', 'File name is not empty: ' . $lead_file['file_name']); - if (! empty($lead_file['file_name'])) { - log_message('error', 'File name is not empty: ' . $lead_file['file_name']); - - $attachments[] = [ - 'fileName' => $lead_file['file_name'], - 'filePath' => $fullPath, - ]; - } else { - log_message('error', 'File name is empty for ID: ' . $id); - } + $attachments[] = [ + 'fileName' => $lead_file['file_name'], + 'filePath' => $fullPath, + ]; } else { - log_message('error', 'File does not exist at path: ' . $fullPath); + log_message('error', 'File does not exist at path for: ' . $lead_file['file_name']); } } else { log_message('warning', 'No active lead file found for ID: ' . $id . ' and lead_id: ' . $lead_id); @@ -7463,15 +7467,17 @@ class LeadsController extends BaseController return ['status' => 'failed', 'message' => 'Opportunity data not found']; } - $file_name_with_path = WRITEPATH . "/uploads/lead_files/" . $lead_data['file_name']; + $file_name_with_path = ! empty($lead_data['file_name']) + ? storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']) + : null; if ($lead_data['file_name']) { //check physical file - if (! file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || ! file_exists($file_name_with_path)) { $message = "Lead Physcial file not found"; - $this->myLogger->logme('error', ($message . ' for file ' . $file_name_with_path)); + $this->myLogger->logme('error', ($message . ' for file ' . ($lead_data['file_name'] ?? ''))); return ['status' => 'failed', 'message' => 'no physical file']; } @@ -7946,15 +7952,28 @@ class LeadsController extends BaseController public function downloadMemberFile($fileName) { - $filePath = WRITEPATH . 'uploads/lead_files/' . $fileName; + $fileName = basename((string) $fileName); + $uploadFilePath = WRITEPATH . 'uploads/lead_files'; + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadFilePath, $fileName); - if (! file_exists($filePath)) { + if (! ($result['success'] ?? false)) { $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + return view('errors/404', $data); } - // force download - return $this->response->download($filePath, null); + $downloadAs = storage_upload_display_name($fileName); + + 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); + } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } // ----------- MEMBER DATA VALIDAATION ------------------------------------------------------------------------------------------------------ @@ -8005,11 +8024,13 @@ class LeadsController extends BaseController // dd($family_composition); // get the file path - $file_name_with_path = WRITEPATH . "/uploads/lead_files/" . $lead_data['file_name']; - $this->myLogger->logme('error', "File path: {$file_name_with_path}"); + $file_name_with_path = ! empty($lead_data['file_name']) + ? storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $lead_data['file_name']) + : null; + $this->myLogger->logme('error', "File path: " . ($file_name_with_path ?? '')); //check physical file - if (! file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || ! file_exists($file_name_with_path)) { //file not found update status and reason $message = "Physcial file not found"; $this->myLogger->logme('error', ($message . ' for lead id ' . $lead_id)); @@ -8257,10 +8278,10 @@ class LeadsController extends BaseController // dd($error_data); // return $error_data; - $file_name_with_path = WRITEPATH . "/uploads/lead_files/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $file['file_name']); //check the file exist or not - if (! file_exists($file_name_with_path)) { + if (empty($file_name_with_path) || ! file_exists($file_name_with_path)) { $error_message = "File not found"; $this->myLogger->logme('error', ($error_message . ' for file id ' . $lead_id)); return 0; @@ -8363,10 +8384,10 @@ class LeadsController extends BaseController } $fileName = $file_data['file_name']; - $filePath = WRITEPATH . '/uploads/lead_files/' . $fileName; + $filePath = storage_ensure_local_file(WRITEPATH . 'uploads/lead_files', $fileName); // Check if the file exists - if (! file_exists($filePath)) { + if (empty($filePath) || ! file_exists($filePath)) { $error_message = "File not found"; $this->myLogger->logme('error', $error_message . ' for file id ' . $lead_id); $data['message'] = 'Physical File Not Found'; @@ -8423,11 +8444,13 @@ class LeadsController extends BaseController } } - // Create a new filename for the modified Excel file + // Create a temporary error Excel for download only (not persisted to S3) $newFileName = 'error_with_highlight_' . $fileName; - - // Save the modified Excel file to a new location - $newFilePath = WRITEPATH . '/uploads/lead_files/' . $newFileName; + $tmpDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'claim_files_runtime'; + if (! is_dir($tmpDir)) { + @mkdir($tmpDir, 0755, true); + } + $newFilePath = $tmpDir . DIRECTORY_SEPARATOR . $newFileName; $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); $writer->save($newFilePath); diff --git a/app/Controllers/MasterController.php b/app/Controllers/MasterController.php index 9d33863e..f4ea0e39 100755 --- a/app/Controllers/MasterController.php +++ b/app/Controllers/MasterController.php @@ -3144,6 +3144,8 @@ class MasterController extends AdminController 'template_bg' => ROOTPATH . 'public/uploads/template_bg/', 'attachments' => WRITEPATH . 'uploads/attachments/', 'cache' => WRITEPATH . 'cache', + 'storage_runtime' => WRITEPATH . 'cache/storage_runtime/', + 'claim_files_runtime' => WRITEPATH . 'cache/claim_files_runtime/', 'sample_import_excel' => ROOTPATH . 'public/sample_import_excel', 'lead_files' => WRITEPATH . 'uploads/lead_files/', 'claim_files' => WRITEPATH . 'uploads/claim_files/', diff --git a/app/Controllers/MediAssistApiController.php b/app/Controllers/MediAssistApiController.php index a05b8853..71d683f9 100644 --- a/app/Controllers/MediAssistApiController.php +++ b/app/Controllers/MediAssistApiController.php @@ -30,7 +30,7 @@ class MediAssistApiController extends BaseController public function SubmitClaim ($claimId = null) { - helper(['api', 'tpa_claim_push_log']); + helper(['api', 'tpa_claim_push_log', 'utility']); // $url = ''. getenv('MEDI_ASSIST_API_BASE_URL').'/SubmitClaim'; $url = getenv('MEDI_ASSIST_API_BASE_URL_CLAIMSUBMIT'); @@ -76,18 +76,11 @@ class MediAssistApiController extends BaseController // Map DB result to request body if ($data) { - $filePath = $data['filePath'] ?? ''; - - $filename = basename($filePath); - - $fileDir = WRITEPATH . 'uploads/claim_files/'.$filename; - - - if ($fileDir) { - $downloadUrl = base_url('fileDownload?file_path=').$fileDir; - $file_id = $data['fileId'] ?? null; - } else { - $downloadUrl = ''; + $filename = basename((string) ($data['filePath'] ?? '')); + $file_id = $data['fileId'] ?? null; + $downloadUrl = ''; + if (!empty($file_id) && $filename !== '') { + $downloadUrl = storage_claim_file_download_url($filename, $file_id); } $body = [ @@ -883,6 +876,7 @@ class MediAssistApiController extends BaseController public function IRSubmission($claimId = null) // 585 this id for test { + helper('utility'); log_message('error',"MEDI_ASSIST - IR Submission | INIT for ticket_id={$claimId}"); // 1. FETCH TICKET DETAILS @@ -930,13 +924,9 @@ class MediAssistApiController extends BaseController if (!empty($file['url']) && $file['file_type'] == 2) { $filename = basename($file['url']); - $fileDir = WRITEPATH . 'uploads/claim_files/' . $filename; - - if (file_exists($fileDir)) { - $downloadUrl = base_url('fileDownload?file_path=') . $fileDir; - } else { - $downloadUrl = ""; - log_message('error',"MEDI_ASSIST - IR Submission File NOT FOUND on server → {$fileDir}"); + $downloadUrl = storage_claim_file_download_url($filename, $file['id'] ?? null); + if ($downloadUrl === '') { + log_message('error',"MEDI_ASSIST - IR Submission File NOT FOUND in local/S3 → {$filename}"); } log_message('error',"MEDI_ASSIST - IR Submission Attachment Ready: {$filename} | URL={$downloadUrl}"); diff --git a/app/Controllers/NonEbClaimController.php b/app/Controllers/NonEbClaimController.php index 9f6ec697..b03f232f 100644 --- a/app/Controllers/NonEbClaimController.php +++ b/app/Controllers/NonEbClaimController.php @@ -1041,7 +1041,7 @@ class NonEbClaimController extends BaseController } $uploadPath = WRITEPATH . 'uploads/non_eb_asset_files/'; - $fileName = file_Upload($file, $uploadPath, UPLOAD_EXT_ASSET_FILES); + $fileName = storage_file_Upload($file, $uploadPath, UPLOAD_EXT_ASSET_FILES); return !empty($fileName) ? $fileName : null; } @@ -1105,7 +1105,7 @@ class NonEbClaimController extends BaseController return $this->respond(['status' => false, 'message' => 'Failed to upload file']); } else { $file_path = WRITEPATH . 'uploads/claim_files/'; - $file_data = multi_file_Upload($get_file_data, $file_path, $data['docs_name'], UPLOAD_EXT_CLAIM_DOCS); + $file_data = storage_multi_file_Upload($get_file_data, $file_path, $data['docs_name'], UPLOAD_EXT_CLAIM_DOCS); if (!empty($file_data)) { $insertArr = []; foreach ($file_data as $f) { @@ -1151,8 +1151,7 @@ class NonEbClaimController extends BaseController if (empty(trim($id ?? ''))) { return $this->response->setJSON(['status' => false, 'message' => 'Invalid ID']); } - $updated = $this->claimFilesModel->where('id', $id)->set(['is_active' => 0])->update(); - if ($updated) { + if (storage_soft_delete_claim_file((int) $id)) { return $this->response->setJSON(['status' => true, 'message' => 'File removed successfully.']); } return $this->response->setJSON(['status' => false, 'message' => 'Failed to remove file.']); diff --git a/app/Controllers/NotificationController.php b/app/Controllers/NotificationController.php index 4b743cc3..0df383ef 100755 --- a/app/Controllers/NotificationController.php +++ b/app/Controllers/NotificationController.php @@ -364,7 +364,7 @@ class NotificationController extends AdminController // Define upload path and attempt file upload $uploadFilePath = WRITEPATH . 'uploads/attachments'; $uploadedFile = $this->request->getFile('file'); - $fileName = file_Upload_for_lead($uploadedFile, $uploadFilePath, UPLOAD_EXT_MAIL_ATTACHMENTS); + $fileName = storage_file_Upload($uploadedFile, $uploadFilePath, UPLOAD_EXT_MAIL_ATTACHMENTS); if ($fileName) { // Prepare data for insertion @@ -396,11 +396,14 @@ class NotificationController extends AdminController public function removeAttachmentsForMailTemplates($id, $client_id, $template_name) { $attachment_data_for_unlink_file = $this->MailAttachmentModel->where('id', $id)->first(); - $file_path = WRITEPATH . $attachment_data_for_unlink_file['file_path']; + $storedName = basename((string) ($attachment_data_for_unlink_file['file_name'] ?? '')); if ($this->MailAttachmentModel->where('id', $id)->delete()) { - unlink($file_path); + if ($storedName !== '') { + $storage = \Config\Services::getFileStorageService(); + $storage->delete(WRITEPATH . 'uploads/attachments', $storedName); + } $find_notification = $this->notificationModel->where('client_id', $client_id)->where('template_name', $template_name)->first(); $attachmentDatas = $this->MailAttachmentModel->where('notification_id', $find_notification['id'])->where('is_active', 1)->findAll(); diff --git a/app/Controllers/PolicyTransactionController.php b/app/Controllers/PolicyTransactionController.php index 818c3d1f..bfda0f28 100644 --- a/app/Controllers/PolicyTransactionController.php +++ b/app/Controllers/PolicyTransactionController.php @@ -3694,8 +3694,8 @@ class PolicyTransactionController extends BaseController if (!empty($docName) && $file->isValid() && !$file->hasMoved()) { - // Upload the file - $uploadedFileName = file_Upload_for_lead($file, $uploadFilePath, UPLOAD_EXT_POLICY_DOCS); + // Upload via FileStorageService (S3-only when enabled; unique storage key) + $uploadedFileName = storage_file_Upload($file, $uploadFilePath, UPLOAD_EXT_POLICY_DOCS); if ($uploadedFileName) { // Prepare data for each document upload @@ -4489,10 +4489,8 @@ class PolicyTransactionController extends BaseController return $this->respond(['dataStatus' => false, 'code' => 400, 'message' => 'File not found'], 400); } - $is_moved = $avatar->move(WRITEPATH . 'uploads/statements/'); - if ($is_moved) { - $filename = $avatar->getName(); - // Handle successful upload, e.g., log success or further processing + $filename = storage_file_Upload($avatar, WRITEPATH . 'uploads/statements/', UPLOAD_EXT_EXCEL); + if ($filename !== '') { $this->myLogger->logme("error", 'Statement File moved successful'); } else { $this->myLogger->logme("error", 'Statement File move failed'); @@ -4588,7 +4586,7 @@ class PolicyTransactionController extends BaseController //file not found in DB return array('status' => false, 'msg' => 'statement file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/statements', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -4708,7 +4706,7 @@ class PolicyTransactionController extends BaseController $this->insurerStatements->where('id', $file_id)->set(['file_status' => 'failed', 'reason' => json_encode(['error_code' => 0, 'error_data' => 'statement file not found in DB'])])->update(); return array('status' => false, 'msg' => 'statement file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/statements', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -4858,7 +4856,7 @@ class PolicyTransactionController extends BaseController //file not found in DB return array('status' => false, 'msg' => 'statement file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/statements', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -5034,7 +5032,7 @@ class PolicyTransactionController extends BaseController $this->insurerStatements->where('id', $file_id)->set(['file_status' => 'failed', 'reason' => json_encode(['error_code' => 0, 'error_data' => 'statement file not found in DB'])])->update(); return array('status' => false, 'error_code' => 0, 'error_data' => 'statement file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/statements', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -5211,7 +5209,7 @@ class PolicyTransactionController extends BaseController //file not found in DB return array('status' => false, 'error_code' => 0, 'error_data' => 'statement file not found in DB'); } - $file_name_with_path = WRITEPATH . "/uploads/statements/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/statements', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -5522,13 +5520,22 @@ class PolicyTransactionController extends BaseController } $fileName = basename($statement['file_name']); - $filePath = WRITEPATH . 'uploads/statements/' . $fileName; + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download(WRITEPATH . 'uploads/statements', $fileName); - if (!is_file($filePath)) { + if (! ($result['success'] ?? false)) { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Statement file not found'); } - return $this->response->download($filePath, null)->setFileName($fileName); + $downloadAs = storage_upload_display_name($fileName); + 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 \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound('Statement file not found'); } public function getFileErr() @@ -6233,13 +6240,10 @@ class PolicyTransactionController extends BaseController return $this->respond(['status' => false, 'code' => 400, 'message' => 'File not found'], 400); } - $is_moved = $avatar->move(WRITEPATH . 'uploads/bds_dump_excel/'); - - if ($is_moved) { - $filename = $avatar->getName(); - $fileSize = $avatar->getSize(); // File size in bytes - $fileSize = $fileSize / (1024 * 1024); // Convert to MB + $filename = storage_file_Upload($avatar, WRITEPATH . 'uploads/bds_dump_excel/', UPLOAD_EXT_EXCEL); + if ($filename !== '') { + $fileSize = method_exists($avatar, 'getSize') ? ($avatar->getSize() / (1024 * 1024)) : 0; $this->myLogger->logme("error", 'File move successful'); } else { $this->myLogger->logme("error", 'File move failed'); @@ -6271,32 +6275,33 @@ class PolicyTransactionController extends BaseController public function downloadBDSDumpFile($file_id) { - // $actionType = $this->request->getGet(); $file_data = $this->bdsDumpModel->where('id', $file_id)->first(); - $fileName = $file_data['file_name']; + if (empty($file_data) || empty($file_data['file_name'])) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } - $filePath = WRITEPATH . '/uploads/bds_dump_excel/' . $fileName; + $fileName = basename((string) $file_data['file_name']); + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download(WRITEPATH . 'uploads/bds_dump_excel', $fileName); try { - - // Check if the file exists - if (file_exists($filePath)) { - // Set the appropriate MIME type - $mimeType = mime_content_type($filePath); - - // Send the file to the client for download - return $this->response->download($filePath, null, $mimeType); - } else { - - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + if ($result['success'] ?? false) { + $downloadAs = storage_upload_display_name($fileName); + 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); + } } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } catch (\Exception $e) { - // Handle any exceptions - $errorMessage = $e->getMessage(); - $this->myLogger->logme('error', $errorMessage); - // You can return an error response here - echo $errorMessage; + $this->myLogger->logme('error', $e->getMessage()); + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } } @@ -6311,7 +6316,7 @@ class PolicyTransactionController extends BaseController return array('status' => false, 'message' => 'File not found in Database'); } - $file_name_with_path = WRITEPATH . "/uploads/bds_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/bds_dump_excel', $file['file_name']); //check physical file exist if (!file_exists($file_name_with_path)) { @@ -6499,7 +6504,7 @@ class PolicyTransactionController extends BaseController return array('status' => false, 'message' => 'File not found in Database'); } - $file_name_with_path = WRITEPATH . "/uploads/bds_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/bds_dump_excel', $file['file_name']); //check physical file exist if (!file_exists($file_name_with_path)) { @@ -6792,7 +6797,7 @@ class PolicyTransactionController extends BaseController { // $file_name = "claims_dump_form_client.xlsx"; // Load the Excel file - $file_name_with_path = WRITEPATH . "/uploads/bds_dump_excel/" . $file_name; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/bds_dump_excel', $file_name); $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $worksheet = $spreadsheet->getActiveSheet(); @@ -6822,7 +6827,7 @@ class PolicyTransactionController extends BaseController $error_data = json_decode($file['reason']); // dd($error_data); - $file_name_with_path = WRITEPATH . "/uploads/bds_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/bds_dump_excel', $file['file_name']); // Kint::dump(file_exists($file_name_with_path)); die; //check the file exist or not @@ -7230,4 +7235,53 @@ class PolicyTransactionController extends BaseController } } + /** + * Download policy transaction (pt_files) document from local/S3 storage. + */ + public function downloadPtDocument($fileOrId = null) + { + if ($fileOrId === null || $fileOrId === '') { + $data['message'] = 'No file specified.'; + return view('errors/404', $data); + } + + $uploadFilePath = WRITEPATH . 'uploads/client_kyc_documents'; + $storedFileName = null; + $downloadAs = null; + + if (ctype_digit((string) $fileOrId)) { + $record = $this->PTFileModel->find((int) $fileOrId); + if (empty($record) || empty($record['file_name'])) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } + $storedFileName = basename((string) $record['file_name']); + $downloadAs = storage_upload_display_name($storedFileName); + } else { + $storedFileName = basename((string) $fileOrId); + $downloadAs = storage_upload_display_name($storedFileName); + } + + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadFilePath, $storedFileName); + + if (! ($result['success'] ?? false)) { + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); + } + + 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); + } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); + } + } diff --git a/app/Controllers/RestAuthenticationController.php b/app/Controllers/RestAuthenticationController.php index 5a029fff..b3b47497 100755 --- a/app/Controllers/RestAuthenticationController.php +++ b/app/Controllers/RestAuthenticationController.php @@ -825,6 +825,7 @@ class RestAuthenticationController extends AdminController $HRAccessData['post_client_id'] = md5($HRAccessData['post_client_id']); $HRAccessData['claims_sub_menu'] = $claimsSubMenu; + print_r($HRAccessData); die; $token = JWTToken::encode($HRAccessData); $getAllhrData[$key]['token'] = $token; diff --git a/app/Controllers/TicketController.php b/app/Controllers/TicketController.php index a23a8711..c549be78 100644 --- a/app/Controllers/TicketController.php +++ b/app/Controllers/TicketController.php @@ -2419,34 +2419,27 @@ class TicketController extends BaseController // $actionType = $this->request->getGet(); $claimFiles = new ClaimFilesModel(); $file_data = $claimFiles->where('id', $claim_file_id)->first(); - // $fileName = $file_data['doc_name']; + if (empty($file_data)) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } - $url = $file_data['url']; - $parts = explode('/', $url); - $fileName = end($parts); - - $filePath = WRITEPATH . '/uploads/claim_files/' . $fileName; + $fileName = $this->resolveClaimFileDiskName($file_data); try { - - // Check if the file exists - if (file_exists($filePath)) { - // Set the appropriate MIME type - $mimeType = mime_content_type($filePath); - - // Send the file to the client for download - return $this->response->download($filePath, null, $mimeType); - } else { - - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + $download = $this->downloadFileFromStorage(WRITEPATH . 'uploads/claim_files', $fileName); + if ($download !== null) { + return $download; } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } catch (\Exception $e) { // Handle any exceptions $errorMessage = $e->getMessage(); $this->myLogger->logme('error', $errorMessage); - // You can return an error response here - echo $errorMessage; + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } } @@ -2466,21 +2459,35 @@ class TicketController extends BaseController throw PageNotFoundException::forPageNotFound(); } - $url = $file_record['url']; - $parts = explode('/', $url); - $fileName = end($parts); - $filePath = WRITEPATH . '/uploads/claim_files/' . $fileName; - - if (! is_file($filePath)) { - throw PageNotFoundException::forPageNotFound(); - } + $fileName = $this->resolveClaimFileDiskName($file_record); $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION) ?: ''); if (! in_array($ext, ['pdf', 'png', 'jpg', 'jpeg'], true)) { throw PageNotFoundException::forPageNotFound(); } - return $this->response->download($filePath, null, true)->inline()->setFileName($fileName); + $storage = \Config\Services::getFileStorageService(); + $uploadPath = WRITEPATH . 'uploads/claim_files'; + $result = $storage->download($uploadPath, $fileName); + + if (! ($result['success'] ?? false)) { + throw PageNotFoundException::forPageNotFound(); + } + + if (! empty($result['path']) && is_file($result['path'])) { + return $this->response->download($result['path'], null, true)->inline()->setFileName($fileName); + } + + if (! empty($result['content'])) { + $mimeType = getMimeTypeByFileName($fileName) ?: 'application/octet-stream'; + + return $this->response + ->setHeader('Content-Type', $mimeType) + ->setHeader('Content-Disposition', 'inline; filename="' . $fileName . '"') + ->setBody($result['content']); + } + + throw PageNotFoundException::forPageNotFound(); } public function convertHtmlToTextOld($html) @@ -3729,15 +3736,15 @@ class TicketController extends BaseController mkdir($uploadPath, 0755, true); } - $diskFileName = file_Upload_for_lead($file, $uploadPath, ['pdf']); - if (empty($diskFileName)) { + $uploaded = storage_claim_file_Upload($file, $uploadPath, ['pdf']); + if ($uploaded === '') { return ['status' => false, 'message' => 'Failed to upload ' . $documentLabel . ' file.']; } return [ 'status' => true, 'data' => [ - 'file_name' => $diskFileName, + 'file_name' => $uploaded['disk_name'], 'mime_type' => $mime ?: 'application/pdf', ], ]; @@ -3857,7 +3864,7 @@ class TicketController extends BaseController $file_data = []; if(isset($get_file_data) && !empty($get_file_data)){ $file_path = WRITEPATH . 'uploads/claim_files/'; - $file_data = multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); + $file_data = storage_multi_file_Upload($get_file_data, $file_path, $get_docs_name, UPLOAD_EXT_CLAIM_DOCS); } if(!empty($file_data)){ @@ -3980,29 +3987,24 @@ class TicketController extends BaseController { $id = $this->request->getGet('id'); - if (empty(trim($id))) { + if (empty(trim($id ?? ''))) { return $this->response->setJSON([ 'status' => false, 'message' => 'Invalid ID' ]); } - $updated = $this->claimFilesModel - ->where('id', $id) - ->set(['is_active' => 0]) - ->update(); - - if ($updated) { + if (storage_soft_delete_claim_file((int) $id)) { return $this->response->setJSON([ 'status' => true, 'message' => 'URL successfully marked inactive.' ]); - } else { - return $this->response->setJSON([ - 'status' => false, - 'message' => 'Failed to update record.' - ]); } + + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Failed to update record.' + ]); } public function recursive_json_decode($input) @@ -4389,13 +4391,10 @@ class TicketController extends BaseController return $this->respond(['status' => false, 'code' => 400, 'message' => 'File not found'], 400); } - $is_moved = $avatar->move(WRITEPATH . 'uploads/claim_dump_excel/'); - - if ($is_moved) { - $filename = $avatar->getName(); - $fileSize = $avatar->getSize(); // File size in bytes - $fileSize = $fileSize / (1024 * 1024); // Convert to MB + $filename = storage_file_Upload($avatar, WRITEPATH . 'uploads/claim_dump_excel/', UPLOAD_EXT_EXCEL); + if ($filename !== '') { + $fileSize = method_exists($avatar, 'getSize') ? ($avatar->getSize() / (1024 * 1024)) : 0; $this->myLogger->logme("error", 'File move successful'); } else { @@ -4487,34 +4486,30 @@ class TicketController extends BaseController { // $actionType = $this->request->getGet(); $file_data = $this->claimDumpFileModel->where('id', $file_id)->first(); + if (empty($file_data)) { + $data['message'] = 'File record not found'; + return view('errors/404', $data); + } + $fileName = $file_data['file_name']; - $filePath = WRITEPATH . '/uploads/claim_dump_excel/' . $fileName; - try { - - // Check if the file exists - if (file_exists($filePath)) { - // Set the appropriate MIME type - $mimeType = mime_content_type($filePath); - - // Send the file to the client for download - return $this->response->download($filePath, null, $mimeType); - } else { - - $data['message'] = 'The Physical File Not Found'; - echo view('errors/404', $data); + $download = $this->downloadFileFromStorage(WRITEPATH . 'uploads/claim_dump_excel', $fileName); + if ($download !== null) { + return $download; } + + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } catch (\Exception $e) { // Handle any exceptions $errorMessage = $e->getMessage(); $this->myLogger->logme('error', $errorMessage); - // You can return an error response here - echo $errorMessage; + $data['message'] = 'The Physical File Not Found'; + return view('errors/404', $data); } } - /** * Soft-delete a TPA claim dump upload: dump rows + tickets created by that file_id. */ @@ -4566,6 +4561,7 @@ class TicketController extends BaseController ], 200); } } + /** * List dump rows for a file where ticket_id is still NULL (not moved to ticket_master). */ @@ -5174,16 +5170,11 @@ class TicketController extends BaseController return $this->respond(['status' => false, 'code' => 400, 'message' => 'File not found'], 400); } - $is_moved = $file->move(WRITEPATH . 'uploads/claims_mis/'); - - if ($is_moved) { - $file_path = WRITEPATH.'uploads/claims_mis'; - $filename = file_Upload_for_lead($file, $file_path, UPLOAD_EXT_EXCEL); - $fileSize = $file->getSize(); // File size in bytes - $fileSize = $fileSize / (1024 * 1024); // Convert to MB + $filename = storage_file_Upload($file, WRITEPATH . 'uploads/claims_mis/', UPLOAD_EXT_EXCEL); + if ($filename !== '') { $this->myLogger->logme("error", 'File move successful'); - + $request_data = $this->request->getPost(); $data = sanitizeInputArrayAdvanced($request_data); @@ -5194,9 +5185,7 @@ class TicketController extends BaseController if(isset($data['to_date']) && !empty($data['to_date'])){ $data['to_date'] = change_date_format($data['to_date'], 'd/m/Y', 'Y-m-d'); } - if(!empty($file_name)){ - $data['file_name'] = $file; - } + $data['file_name'] = $filename; $response = $this->claimmisFileModel->insert($data); @@ -5260,17 +5249,13 @@ class TicketController extends BaseController return view('errors/404', $data); } - $uploadPath = WRITEPATH . 'uploads/claims_mis/'; - $filePath = $uploadPath . $record['file_name']; - // dd($filePath); - - if (!file_exists($filePath)) { + $download = $this->downloadFileFromStorage(WRITEPATH . 'uploads/claims_mis', $record['file_name']); + if ($download === null) { $data['message'] = 'The Physical File Not Found'; return view('errors/404', $data); } - // Force file download - return $this->response->download($filePath, null)->setFileName($record['file_name']); + return $download; } catch (\Exception $e) { // return $this->failServerError($e->getMessage()); @@ -5280,4 +5265,38 @@ class TicketController extends BaseController } } + private function resolveClaimFileDiskName(array $fileRecord): string + { + $url = (string) ($fileRecord['url'] ?? ''); + if ($url !== '') { + return basename($url); + } + + return basename((string) ($fileRecord['file_name'] ?? '')); + } + + /** + * Download helper for phased local->S3 migration. + * Returns null when file is unavailable in both local and S3. + */ + private function downloadFileFromStorage(string $uploadPath, string $fileName) + { + $storage = \Config\Services::getFileStorageService(); + $result = $storage->download($uploadPath, $fileName); + + if (!($result['success'] ?? false)) { + return null; + } + + if (!empty($result['path']) && is_file($result['path'])) { + return $this->response->download($result['path'], null)->setFileName($fileName); + } + + if (!empty($result['content'])) { + return $this->response->download($fileName, $result['content'])->setFileName($fileName); + } + + return null; + } + } diff --git a/app/Controllers/TicketServiceController.php b/app/Controllers/TicketServiceController.php index 3dcc45f4..e034d40c 100644 --- a/app/Controllers/TicketServiceController.php +++ b/app/Controllers/TicketServiceController.php @@ -965,7 +965,7 @@ class TicketServiceController extends AdminController return array('status' => false, 'message' => 'File not found in Database'); } - $file_name_with_path = WRITEPATH . "/uploads/claim_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/claim_dump_excel', $file['file_name']); //check physical file exist if (!file_exists($file_name_with_path)) { @@ -1174,7 +1174,7 @@ class TicketServiceController extends AdminController return array('status' => false, 'message' => 'File not found in Database'); } - $file_name_with_path = WRITEPATH . "/uploads/claim_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/claim_dump_excel', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -1360,7 +1360,7 @@ class TicketServiceController extends AdminController return array('status' => false, 'message' => 'File not found in Database'); } - $file_name_with_path = WRITEPATH . "/uploads/claim_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/claim_dump_excel', $file['file_name']); //check physical file if (!file_exists($file_name_with_path)) { @@ -1478,7 +1478,7 @@ class TicketServiceController extends AdminController { // $file_name = "claims_dump_form_client.xlsx"; // Load the Excel file - $file_name_with_path = WRITEPATH . "/uploads/claim_dump_excel/" . $file_name; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/claim_dump_excel', $file_name); $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($file_name_with_path); $worksheet = $spreadsheet->getActiveSheet(); @@ -1751,7 +1751,7 @@ class TicketServiceController extends AdminController $error_data = json_decode($file['reason']); // dd($error_data); - $file_name_with_path = WRITEPATH . "/uploads/claim_dump_excel/" . $file['file_name']; + $file_name_with_path = storage_ensure_local_file(WRITEPATH . 'uploads/claim_dump_excel', $file['file_name']); //check the file exist or not if (!file_exists($file_name_with_path)) { @@ -1870,7 +1870,7 @@ class TicketServiceController extends AdminController ]; } - $filePath = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . 'claim_dump_excel' . DIRECTORY_SEPARATOR . $fileData['file_name']; + $filePath = storage_ensure_local_file(WRITEPATH . 'uploads/claim_dump_excel', $fileData['file_name']); if ($requirePhysicalFile && !is_file($filePath)) { return [ diff --git a/app/Controllers/VidalApiController.php b/app/Controllers/VidalApiController.php index 0fec2c73..f754a7fd 100644 --- a/app/Controllers/VidalApiController.php +++ b/app/Controllers/VidalApiController.php @@ -134,7 +134,7 @@ class VidalApiController extends BaseController public function SubmitClaim ($claimId = null) //515 { - helper(['api', 'tpa_claim_push_log']); + helper(['api', 'tpa_claim_push_log', 'utility']); // Fetch the data from DB $data = $this->db->table('ticket_master tm') @@ -181,12 +181,21 @@ class VidalApiController extends BaseController $filePath = $data['filePath'] ?? ''; $filename = basename($filePath); - $filePath = WRITEPATH . 'uploads/claim_files/'.$filename; + $resolved = storage_resolve_claim_file_path(WRITEPATH . 'uploads/claim_files', $filename); + if (!($resolved['success'] ?? false) || empty($resolved['path'])) { + tpa_claim_push_log($claimId, "VIDAL - Claim Push | Submit claim failed - File not found in local/S3: {$filename}"); + return $this->response->setJSON([ + 'status' => false, + 'message' => 'File upload failed', + 'data' => ['message' => 'Claim file not found in storage'], + ]); + } // dd($data); // Upload file first - $upload = $this->uploadFileToVidal($filePath, $filename, $claimId); + $upload = $this->uploadFileToVidal($resolved['path'], $filename, $claimId); + storage_cleanup_temp_claim_file($resolved); if ($upload['status'] !== true) { tpa_claim_push_log($claimId, "VIDAL - Claim Push | Submit claim failed - File upload failed"); return $this->response->setJSON([ @@ -1663,6 +1672,7 @@ class VidalApiController extends BaseController public function IRSubmission($claimId = null) { + helper('utility'); log_message('error', "VIDAL - IR Submission | INIT for ticket_id={$claimId}"); // 1. FETCH TICKET DETAILS @@ -1718,14 +1728,20 @@ class VidalApiController extends BaseController foreach ($fileData as $file) { - if (empty($file['filePath'])) { + $storedPath = (string) ($file['filePath'] ?? $file['url'] ?? ''); + if ($storedPath === '') { continue; } - $filename = basename($file['filePath']); - $fullPath = WRITEPATH . 'uploads/claim_files/' . $filename; + $filename = basename($storedPath); + $resolved = storage_resolve_claim_file_path(WRITEPATH . 'uploads/claim_files', $filename); + if (!($resolved['success'] ?? false) || empty($resolved['path'])) { + log_message('error', "VIDAL - IR Submission FAILED → File not found in local/S3 | {$filename}"); + continue; + } - $upload = $this->uploadFileToVidal($fullPath, $filename); + $upload = $this->uploadFileToVidal($resolved['path'], $filename); + storage_cleanup_temp_claim_file($resolved); if (empty($upload['status']) || $upload['status'] !== true) { log_message('error', "VIDAL - IR Submission FAILED → File upload failed"); diff --git a/app/Controllers/VoloApiController.php b/app/Controllers/VoloApiController.php index c256ecfa..09b02f92 100644 --- a/app/Controllers/VoloApiController.php +++ b/app/Controllers/VoloApiController.php @@ -682,7 +682,7 @@ class VoloApiController extends BaseController */ public function SubmitClaim($claimId = null) { - helper(['api', 'tpa_claim_push_log']); + helper(['api', 'tpa_claim_push_log', 'utility']); $data = $this->db->table('ticket_master tm') ->select(' @@ -713,14 +713,12 @@ class VoloApiController extends BaseController $file_id = $data['fileId'] ?? null; $filename = basename($data['filePath']); - $pdfPath = WRITEPATH . 'uploads/claim_files/' . $filename; - if (!is_readable($pdfPath)) { - tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | PDF not readable'); + $publicUrl = storage_claim_file_download_url($filename, $file_id); + if ($publicUrl === '') { + tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | PDF not available on S3/local'); return ['status' => false, 'message' => 'Claim Push FAILED | PDF not found on server']; } - $publicUrl = base_url('fileDownload?file_path=') . $pdfPath; - $entityId = $this->resolveEntityId($data['policyNo'] ?? ''); if ($entityId === null) { tpa_claim_push_log($claimId, 'VOLO - Claim Push FAILED | claimId: ' . $claimId . ' | entity id not resolved'); diff --git a/app/Helpers/merge_pdf_helper.php b/app/Helpers/merge_pdf_helper.php index e1ccc426..43d7ed0a 100644 --- a/app/Helpers/merge_pdf_helper.php +++ b/app/Helpers/merge_pdf_helper.php @@ -86,22 +86,26 @@ if (! function_exists('merge_ticket_pdfs')) { $sourceFiles = []; foreach ($rows as $row) { - $full = merge_ticket_pdf_resolve_disk_path($row, $uploadDir); + $full = merge_ticket_pdf_resolve_disk_path($row, $uploadDir, true); if ($full !== null) { $mime = merge_ticket_pdf_resolve_mime($full, (string) ($row['mime_type'] ?? '')); if (! in_array($mime, $opts['include_mime_types'], true)) { log_message('error', "merge_ticket_pdfs | unsupported source mime {$mime} | claim_file_id={$row['id']} | path={$full}"); + if (strpos($full, 'claim_files_runtime') !== false) { + @unlink($full); + } continue; } $sourceFiles[] = [ - 'path' => $full, - 'mime' => $mime, - 'id' => $row['id'] ?? null, + 'path' => $full, + 'mime' => $mime, + 'id' => $row['id'] ?? null, + 'is_temp' => strpos($full, 'claim_files_runtime') !== false, ]; } else { - $name = ! empty($row['url']) ? $row['url'] : ($row['file_name'] ?? ''); - log_message('error', "merge_ticket_pdfs | missing file on disk | claim_file_id={$row['id']} | path=" . $uploadDir . basename((string) $name)); + $name = ! empty($row['url']) ? $row['url'] : ($row['file_name'] ?? ''); + log_message('error', "merge_ticket_pdfs | missing file in local/S3 | claim_file_id={$row['id']} | path=" . $uploadDir . basename((string) $name)); } } @@ -110,6 +114,14 @@ if (! function_exists('merge_ticket_pdfs')) { return $result; } + $cleanupMergeTemps = static function (array $files): void { + foreach ($files as $sourceFile) { + if (! empty($sourceFile['is_temp']) && ! empty($sourceFile['path']) && is_file($sourceFile['path'])) { + @unlink($sourceFile['path']); + } + } + }; + $result['source_count'] = count($sourceFiles); log_message( 'error', @@ -162,22 +174,39 @@ if (! function_exists('merge_ticket_pdfs')) { } if ($totalPages === 0) { + $cleanupMergeTemps($sourceFiles); $result['message'] = 'All source PDF/image files failed to import'; return $result; } $mpdf->Output($mergedPath, Destination::FILE); } catch (\Throwable $e) { + $cleanupMergeTemps($sourceFiles); log_message('error', 'merge_ticket_pdfs | mpdf failure | ticket_id=' . $ticket_master_id . ' | ' . $e->getMessage()); $result['message'] = 'Merge failed: ' . $e->getMessage(); return $result; } if (! is_file($mergedPath)) { + $cleanupMergeTemps($sourceFiles); $result['message'] = 'Merged file was not created'; return $result; } + $storage = \Config\Services::getFileStorageService(); + if ($storage->usesS3()) { + $uploadResult = $storage->upload($mergedPath, rtrim($uploadDir, '/\\'), $mergedName); + if (! ($uploadResult['success'] ?? false)) { + log_message('error', 'merge_ticket_pdfs | failed to mirror merged file to S3 | ticket_id=' . $ticket_master_id . ' | ' . json_encode($uploadResult)); + @unlink($mergedPath); + $cleanupMergeTemps($sourceFiles); + $result['message'] = 'Merged file upload to storage failed'; + return $result; + } + // S3-only: drop permanent local merged copy after successful upload. + @unlink($mergedPath); + } + if ($opts['replace']) { $claimFiles ->where('ticket_id', $ticket_master_id) @@ -203,6 +232,8 @@ if (! function_exists('merge_ticket_pdfs')) { $insertedId = $claimFiles->insert($insertData); + $cleanupMergeTemps($sourceFiles); + if (! $insertedId) { log_message('error', 'merge_ticket_pdfs | DB insert failed for merged file | ticket_id=' . $ticket_master_id); @unlink($mergedPath); @@ -356,7 +387,7 @@ if (! function_exists('merge_ticket_pdf_resolve_disk_path')) { /** * Resolve on-disk path for a claim_files row (url and/or file_name). */ - function merge_ticket_pdf_resolve_disk_path(array $row, string $uploadDir): ?string + function merge_ticket_pdf_resolve_disk_path(array $row, string $uploadDir, bool $allowTempDownload = true): ?string { $candidates = []; if (! empty($row['url'])) { @@ -374,6 +405,14 @@ if (! function_exists('merge_ticket_pdf_resolve_disk_path')) { if (is_file($full) && is_readable($full)) { return $full; } + + if ($allowTempDownload) { + helper('utility'); + $resolved = storage_resolve_claim_file_path(rtrim($uploadDir, '/\\'), $name); + if (($resolved['success'] ?? false) && ! empty($resolved['path']) && is_file($resolved['path'])) { + return $resolved['path']; + } + } } return null; @@ -489,8 +528,22 @@ if (! function_exists('merge_ticket_manual_merge_status')) { $sourceRowCount++; - $full = merge_ticket_pdf_resolve_disk_path($row, $uploadDir); + $full = merge_ticket_pdf_resolve_disk_path($row, $uploadDir, false); if ($full === null) { + helper('utility'); + $name = basename((string) (! empty($row['url']) ? $row['url'] : ($row['file_name'] ?? ''))); + if ($name === '') { + continue; + } + $storage = \Config\Services::getFileStorageService(); + $available = $storage->exists(rtrim($uploadDir, '/\\'), $name); + if (! $available) { + continue; + } + $mime = merge_ticket_pdf_resolve_mime($name, (string) ($row['mime_type'] ?? '')); + if (in_array($mime, $opts['include_mime_types'], true)) { + $mergeableCount++; + } continue; } diff --git a/app/Helpers/sendMailNotification.php b/app/Helpers/sendMailNotification.php index 5327f8d5..7371328a 100755 --- a/app/Helpers/sendMailNotification.php +++ b/app/Helpers/sendMailNotification.php @@ -34,10 +34,13 @@ class sendMailNotification $attachments = []; foreach ($attachment_data as $data) { - $attachments[] = [ - "filePath" => WRITEPATH . $data['file_path'], - "fileName" => $data['file_name'] - ]; + $resolved = storage_resolve_mail_attachment_path($data['file_path'] ?? null, $data['file_name'] ?? null); + if (($resolved['success'] ?? false) && ! empty($resolved['path'])) { + $attachments[] = [ + "filePath" => $resolved['path'], + "fileName" => $resolved['display_name'] ?? $data['file_name'], + ]; + } } $mail = $dataToInsert['email_corporate']; @@ -125,10 +128,13 @@ class sendMailNotification $attachments = []; foreach ($attachment_data as $data) { - $attachments[] = [ - "filePath" => WRITEPATH . $data['file_path'], - "fileName" => $data['file_name'] - ]; + $resolved = storage_resolve_mail_attachment_path($data['file_path'] ?? null, $data['file_name'] ?? null); + if (($resolved['success'] ?? false) && ! empty($resolved['path'])) { + $attachments[] = [ + "filePath" => $resolved['path'], + "fileName" => $resolved['display_name'] ?? $data['file_name'], + ]; + } } @@ -1268,10 +1274,13 @@ class sendMailNotification $attachments = []; foreach ($attachment_data as $data) { - $attachments[] = [ - "filePath" => WRITEPATH . $data['file_path'], - "fileName" => $data['file_name'] - ]; + $resolved = storage_resolve_mail_attachment_path($data['file_path'] ?? null, $data['file_name'] ?? null); + if (($resolved['success'] ?? false) && ! empty($resolved['path'])) { + $attachments[] = [ + "filePath" => $resolved['path'], + "fileName" => $resolved['display_name'] ?? $data['file_name'], + ]; + } } // print_r($attachments); die; @@ -1361,10 +1370,13 @@ class sendMailNotification $attachments = []; foreach ($attachment_data as $data) { - $attachments[] = [ - "filePath" => WRITEPATH . $data['file_path'], - "fileName" => $data['file_name'] - ]; + $resolved = storage_resolve_mail_attachment_path($data['file_path'] ?? null, $data['file_name'] ?? null); + if (($resolved['success'] ?? false) && ! empty($resolved['path'])) { + $attachments[] = [ + "filePath" => $resolved['path'], + "fileName" => $resolved['display_name'] ?? $data['file_name'], + ]; + } } $mail_content = $notification['mail_content']; @@ -1416,10 +1428,13 @@ class sendMailNotification $attachments = []; foreach ($attachment_data as $data) { - $attachments[] = [ - "filePath" => WRITEPATH . $data['file_path'], - "fileName" => $data['file_name'] - ]; + $resolved = storage_resolve_mail_attachment_path($data['file_path'] ?? null, $data['file_name'] ?? null); + if (($resolved['success'] ?? false) && ! empty($resolved['path'])) { + $attachments[] = [ + "filePath" => $resolved['path'], + "fileName" => $resolved['display_name'] ?? $data['file_name'], + ]; + } } $mail_content = $notification['mail_content']; diff --git a/app/Helpers/utility_helper.php b/app/Helpers/utility_helper.php index 00955f17..8c35373a 100755 --- a/app/Helpers/utility_helper.php +++ b/app/Helpers/utility_helper.php @@ -99,6 +99,181 @@ if (! function_exists('file_Upload')) { } } +/** + * Extract original/display filename from a unique storage key. + * Unique keys are stored as: {timestamp}_{hex}_{sanitizedOriginalName} + * Legacy plain filenames are returned unchanged. + */ +if (! function_exists('storage_upload_display_name')) { + function storage_upload_display_name(?string $storedName): string + { + $storedName = (string) $storedName; + if ($storedName === '') { + return ''; + } + + if (preg_match('/^\d+_[a-f0-9]+_(.+)$/i', $storedName, $matches)) { + return $matches[1]; + } + + return $storedName; + } +} + +/** + * Build a collision-safe storage filename while preserving the original name for display/download. + */ +if (! function_exists('storage_unique_upload_name')) { + function storage_unique_upload_name(string $originalName): string + { + $displayName = sanitize_upload_filename($originalName); + return time() . '_' . bin2hex(random_bytes(8)) . '_' . $displayName; + } +} + +/** + * Upload via FileStorageService (S3 wrapper). + * Uses folder path last segment as the S3 folder/bucket prefix. + * Stores a unique disk/S3 key so same original names never overwrite each other. + * + * Env RETAIN_LOCAL (default false): + * - false / missing: S3-only → temp stage → upload → delete local + * - true: dual-write → keep permanent local copy under $filepath after S3 upload + * + * Processors should still use storage_ensure_local_file() when local may be missing. + * + * @param bool $retainLocal Unused; behaviour is controlled by env RETAIN_LOCAL only. + */ +if (! function_exists('storage_file_Upload')) { + function storage_file_Upload($fileToUpload, $filepath, array $allowedExtensions = UPLOAD_ALLOWED_EXTENSIONS, bool $retainLocal = false) + { + if ($fileToUpload === null || ! $fileToUpload->isValid() || $fileToUpload->hasMoved()) { + log_message('error', 'storage_file_Upload validation failed | ' . json_encode([ + 'filepath' => $filepath, + 'reason' => $fileToUpload === null ? 'file_null' : (!$fileToUpload->isValid() ? 'invalid_file' : 'already_moved'), + 'error' => (is_object($fileToUpload) && method_exists($fileToUpload, 'getErrorString')) ? $fileToUpload->getErrorString() : null, + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + return ""; + } + + if (! validate_upload_extension($fileToUpload, $allowedExtensions)) { + log_message('error', 'storage_file_Upload extension rejected | ' . json_encode([ + 'filepath' => $filepath, + 'client_name' => method_exists($fileToUpload, 'getClientName') ? $fileToUpload->getClientName() : null, + 'extension' => method_exists($fileToUpload, 'getExtension') ? $fileToUpload->getExtension() : null, + 'allowed_extensions' => $allowedExtensions, + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + return ""; + } + + $originalName = method_exists($fileToUpload, 'getClientName') && $fileToUpload->getClientName() + ? $fileToUpload->getClientName() + : $fileToUpload->getName(); + $fileName = storage_unique_upload_name($originalName); + $storage = \Config\Services::getFileStorageService(); + $filepath = rtrim($filepath, '/\\'); + + // Env controls retention; missing / empty / false => do not keep local when on S3. + $retainLocal = storage_env_retain_local(); + + if ($storage->usesS3()) { + if ($retainLocal) { + if (! is_dir($filepath)) { + mkdir($filepath, 0755, true); + } + $fileToUpload->move($filepath . DIRECTORY_SEPARATOR, $fileName); + $localPath = $filepath . DIRECTORY_SEPARATOR . $fileName; + $result = $storage->upload($localPath, $filepath, $fileName); + + if (! ($result['success'] ?? false)) { + @unlink($localPath); + log_message('error', 'storage_file_Upload retain_local failed | ' . json_encode([ + 'filepath' => $filepath, + 'file_name' => $fileName, + 'message' => $result['message'] ?? null, + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + return ''; + } + + log_message('info', 'storage_file_Upload retain_local completed | ' . json_encode([ + 'filepath' => $filepath, + 'file_name' => $fileName, + 'display_name' => storage_upload_display_name($fileName), + 'retain_local' => true, + 'bucket' => $storage->getBucket(), + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + + return $fileName; + } + + // S3-only: stage briefly, upload, always delete local. + $tmpDir = storage_runtime_temp_dir(); + if (! is_dir($tmpDir)) { + @mkdir($tmpDir, 0755, true); + } + $fileToUpload->move($tmpDir . DIRECTORY_SEPARATOR, $fileName); + $localPath = $tmpDir . DIRECTORY_SEPARATOR . $fileName; + $result = $storage->upload($localPath, $filepath, $fileName); + @unlink($localPath); + + log_message( + ($result['success'] ?? false) ? 'info' : 'error', + 'storage_file_Upload s3_only completed | ' . json_encode([ + 'filepath' => $filepath, + 'file_name' => $fileName, + 'display_name' => storage_upload_display_name($fileName), + 'success' => $result['success'] ?? false, + 'key' => $result['key'] ?? null, + 'message' => $result['message'] ?? null, + 'bucket' => $storage->getBucket(), + 'retain_local' => false, + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + ); + + return ($result['success'] ?? false) ? $fileName : ''; + } + + // Local-driver mode: keep file under the upload folder. + $result = $storage->upload($fileToUpload, $filepath, $fileName); + + log_message( + ($result['success'] ?? false) ? 'info' : 'error', + 'storage_file_Upload completed | ' . json_encode([ + 'filepath' => $filepath, + 'file_name' => $fileName, + 'display_name' => storage_upload_display_name($fileName), + 'success' => $result['success'] ?? false, + 'key' => $result['key'] ?? null, + 'url' => $result['url'] ?? null, + 'message' => $result['message'] ?? null, + 'bucket' => $storage->getBucket(), + 'uses_s3' => $storage->usesS3(), + 'retain_local' => $retainLocal, + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + ); + + return ($result['success'] ?? false) ? $fileName : ""; + } +} + +/** + * Read RETAIN_LOCAL from env. Default / missing / empty => false. + */ +if (! function_exists('storage_env_retain_local')) { + function storage_env_retain_local(): bool + { + $raw = env('RETAIN_LOCAL', null); + if ($raw === null || $raw === '') { + return false; + } + if (is_bool($raw)) { + return $raw; + } + + return filter_var((string) $raw, FILTER_VALIDATE_BOOLEAN); + } +} + if (! function_exists('file_Upload_random_name')) { function file_Upload_random_name($fileToUpload, $filepath, array $allowedExtensions = UPLOAD_ALLOWED_EXTENSIONS, string $namePrefix = ''): array { @@ -285,6 +460,489 @@ if (! function_exists('multi_file_Upload')) { } } +/** + * Claim file upload via FileStorageService (S3) with local disk copy retained + * for merge/TPA integrations during phased migration. + */ +if (! function_exists('storage_multi_file_Upload')) { + function storage_multi_file_Upload($fileToUpload, $filepath, $docs_name = [], array $allowedExtensions = UPLOAD_EXT_CLAIM_DOCS) + { + $uploadedFiles = []; + $filepath = rtrim($filepath, '/\\') . DIRECTORY_SEPARATOR; + $storage = \Config\Services::getFileStorageService(); + + if (! is_dir(rtrim($filepath, '/\\'))) { + mkdir(rtrim($filepath, '/\\'), 0755, true); + } + + $files = is_array($fileToUpload) ? $fileToUpload : [$fileToUpload]; + + foreach ($files as $file) { + if (is_array($file)) { + foreach ($file as $index => $f) { + $uploaded = storage_claim_file_Upload($f, $filepath, $allowedExtensions); + if ($uploaded === '') { + continue; + } + + $uploadedFiles[] = [ + 'file_name' => $uploaded['display_name'], + 'doc_name' => $docs_name[$index] ?? $uploaded['display_name'], + 'file_path' => $uploaded['disk_name'], + ]; + } + } else { + $uploaded = storage_claim_file_Upload($file, $filepath, $allowedExtensions); + if ($uploaded === '') { + continue; + } + + $uploadedFiles[] = [ + 'file_name' => $uploaded['display_name'], + 'doc_name' => $docs_name[0] ?? $uploaded['display_name'], + 'file_path' => $uploaded['disk_name'], + ]; + } + } + + log_message('info', 'storage_multi_file_Upload completed | ' . json_encode([ + 'filepath' => $filepath, + 'uploaded_count' => count($uploadedFiles), + 'uses_s3' => $storage->usesS3(), + 'bucket' => $storage->getBucket(), + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + + return $uploadedFiles; + } +} + +/** + * Single claim file upload: save locally, then mirror to S3 when enabled. + * + * @return array{display_name:string,disk_name:string}|string + */ +if (! function_exists('storage_claim_file_Upload')) { + function storage_claim_file_Upload($fileToUpload, $filepath, array $allowedExtensions = UPLOAD_EXT_CLAIM_DOCS) + { + if ($fileToUpload === null || ! $fileToUpload->isValid() || $fileToUpload->hasMoved()) { + return ''; + } + + if (! validate_upload_extension($fileToUpload, $allowedExtensions)) { + return ''; + } + + $filepath = rtrim($filepath, '/\\') . DIRECTORY_SEPARATOR; + $displayName = sanitize_upload_filename($fileToUpload->getName()); + $diskName = $fileToUpload->getRandomName(); + $storage = \Config\Services::getFileStorageService(); + + if ($storage->usesS3()) { + $tmpDir = storage_runtime_temp_dir(); + if (! is_dir($tmpDir)) { + @mkdir($tmpDir, 0755, true); + } + $fileToUpload->move($tmpDir . DIRECTORY_SEPARATOR, $diskName); + $localPath = $tmpDir . DIRECTORY_SEPARATOR . $diskName; + $result = $storage->upload($localPath, rtrim($filepath, '/\\'), $diskName); + @unlink($localPath); + + log_message( + ($result['success'] ?? false) ? 'info' : 'error', + 'storage_claim_file_Upload s3_only | ' . json_encode([ + 'filepath' => $filepath, + 'disk_name' => $diskName, + 'display_name' => $displayName, + 'success' => $result['success'] ?? false, + 'key' => $result['key'] ?? null, + 'message' => $result['message'] ?? null, + 'bucket' => $storage->getBucket(), + ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) + ); + + if (! ($result['success'] ?? false)) { + return ''; + } + } else { + if (! is_dir($filepath)) { + @mkdir($filepath, 0755, true); + } + $fileToUpload->move($filepath, $diskName); + } + + return [ + 'display_name' => $displayName, + 'disk_name' => $diskName, + ]; + } +} + +/** + * Build a TPA-facing download URL for a claim file. + * Prefers an S3 presigned URL; falls back to app downloadClaimFile/{id} for local mode. + */ +if (! function_exists('storage_claim_file_download_url')) { + function storage_claim_file_download_url(string $diskName, $fileId = null, int $expirationMinutes = 360): string + { + $diskName = basename(trim($diskName)); + if ($diskName === '') { + return ''; + } + + $uploadPath = WRITEPATH . 'uploads/claim_files'; + $storage = \Config\Services::getFileStorageService(); + + 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 ''; + } + + $localPath = $storage->resolveLocalPath($uploadPath, $diskName); + if (! is_file($localPath)) { + return ''; + } + + if ($fileId !== null && $fileId !== '') { + return base_url('downloadClaimFile/' . $fileId); + } + + return ''; + } +} + +/** + * Soft-delete claim_files row and remove unused local/S3 object. + * + * @return bool + */ +if (! function_exists('storage_soft_delete_claim_file')) { + function storage_soft_delete_claim_file(int $claimFileId): bool + { + if ($claimFileId <= 0) { + return false; + } + + $claimFiles = new \App\Models\ClaimFilesModel(); + $record = $claimFiles->find($claimFileId); + if (empty($record)) { + return false; + } + + $updated = $claimFiles->update($claimFileId, ['is_active' => 0]); + if (! $updated) { + return false; + } + + $diskName = basename((string) (! empty($record['url']) ? $record['url'] : ($record['file_name'] ?? ''))); + if ($diskName === '') { + return true; + } + + $stillUsed = $claimFiles + ->where('is_active', 1) + ->where('id !=', $claimFileId) + ->groupStart() + ->where('url', $diskName) + ->orWhere('url', WRITEPATH . 'uploads/claim_files/' . $diskName) + ->orWhere('file_name', $diskName) + ->groupEnd() + ->countAllResults(); + + if ($stillUsed === 0) { + $storage = \Config\Services::getFileStorageService(); + $storage->delete(WRITEPATH . 'uploads/claim_files', $diskName); + } + + return true; + } +} + +/** + * Resolve a claim file from local/S3 into a readable local path. + * + * @return array{success:bool,path:?string,is_temp:bool,message:string} + */ +if (! function_exists('storage_resolve_claim_file_path')) { + function storage_resolve_claim_file_path(string $uploadPath, string $fileName): array + { + $empty = [ + 'success' => false, + 'path' => null, + 'is_temp' => false, + 'message' => 'File not found', + ]; + + $fileName = basename(trim($fileName)); + if ($fileName === '') { + $empty['message'] = 'Invalid file name'; + return $empty; + } + + $storage = \Config\Services::getFileStorageService(); + $uploadPath = rtrim($uploadPath, '/\\'); + $localPath = $storage->resolveLocalPath($uploadPath, $fileName); + if (is_file($localPath) && is_readable($localPath)) { + return [ + 'success' => true, + 'path' => $localPath, + 'is_temp' => false, + 'message' => 'Local file found', + ]; + } + + if (! $storage->usesS3()) { + return $empty; + } + + $download = $storage->download($uploadPath, $fileName); + if (! ($download['success'] ?? false)) { + $empty['message'] = (string) ($download['message'] ?? 'S3 download failed'); + return $empty; + } + + if (! empty($download['path']) && is_file((string) $download['path'])) { + return [ + 'success' => true, + 'path' => (string) $download['path'], + 'is_temp' => false, + 'message' => 'Resolved from storage path', + ]; + } + + $content = $download['content'] ?? null; + if (! is_string($content) || $content === '') { + $empty['message'] = 'S3 returned empty content'; + return $empty; + } + + $tmpDir = rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'claim_files_runtime'; + if (! is_dir($tmpDir)) { + @mkdir($tmpDir, 0755, true); + } + $tmpPath = $tmpDir . DIRECTORY_SEPARATOR . uniqid('claim_', true) . '_' . $fileName; + $written = @file_put_contents($tmpPath, $content); + if ($written === false || ! is_file($tmpPath)) { + $empty['message'] = 'Failed to write temp file'; + return $empty; + } + + storage_register_temp_file($tmpPath); + + return [ + 'success' => true, + 'path' => $tmpPath, + 'is_temp' => true, + 'message' => 'Resolved from S3 content', + ]; + } +} + +if (! function_exists('storage_cleanup_temp_claim_file')) { + function storage_cleanup_temp_claim_file(array $resolved): void + { + if (($resolved['success'] ?? false) && ($resolved['is_temp'] ?? false) && ! empty($resolved['path'])) { + storage_cleanup_temp_file((string) $resolved['path']); + } + } +} + +/** + * Runtime temp directory for S3→local staging (never a permanent upload folder). + */ +if (! function_exists('storage_runtime_temp_dir')) { + function storage_runtime_temp_dir(): string + { + return rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'storage_runtime'; + } +} + +/** + * Register a temp path for automatic cleanup at request/CLI shutdown. + * + * @var list|null + */ +if (! function_exists('storage_register_temp_file')) { + function storage_register_temp_file(string $path): void + { + static $registered = []; + static $shutdownRegistered = false; + + $path = (string) $path; + if ($path === '' || in_array($path, $registered, true)) { + return; + } + $registered[] = $path; + + if (! $shutdownRegistered) { + $shutdownRegistered = true; + register_shutdown_function(static function () use (&$registered): void { + foreach ($registered as $tmp) { + storage_cleanup_temp_file($tmp); + } + $registered = []; + }); + } + } +} + +/** + * Delete a temp runtime file (storage_runtime / claim_files_runtime only). + */ +if (! function_exists('storage_cleanup_temp_file')) { + function storage_cleanup_temp_file(?string $path): void + { + if ($path === null || $path === '' || ! is_file($path)) { + return; + } + + $real = realpath($path); + if ($real === false) { + @unlink($path); + return; + } + + $allowed = []; + foreach ([storage_runtime_temp_dir(), rtrim(WRITEPATH, '/\\') . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'claim_files_runtime'] as $dir) { + if (! is_dir($dir)) { + @mkdir($dir, 0755, true); + } + $resolvedDir = realpath($dir); + if ($resolvedDir !== false) { + $allowed[] = $resolvedDir; + } + } + + foreach ($allowed as $root) { + if (strpos($real, rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR) === 0) { + @unlink($real); + return; + } + } + } +} + +/** + * Resolve a mail-template attachment to a readable local path (local first, else S3→temp). + */ +if (! function_exists('storage_resolve_mail_attachment_path')) { + function storage_resolve_mail_attachment_path(?string $relativePath, ?string $fileName = null): array + { + $name = basename((string) ($fileName ?: $relativePath)); + if ($name === '') { + return ['success' => false, 'path' => null, 'is_temp' => false, 'display_name' => '']; + } + + $uploadDir = WRITEPATH . 'uploads/attachments'; + $legacyPath = $relativePath ? (WRITEPATH . ltrim(str_replace('\\', '/', $relativePath), '/')) : null; + if ($legacyPath && is_file($legacyPath)) { + return [ + 'success' => true, + 'path' => $legacyPath, + 'is_temp' => false, + 'display_name' => storage_upload_display_name($name), + ]; + } + + $resolved = storage_resolve_claim_file_path($uploadDir, $name); + $resolved['display_name'] = storage_upload_display_name($name); + if (($resolved['success'] ?? false) && ($resolved['is_temp'] ?? false) && ! empty($resolved['path'])) { + storage_register_temp_file((string) $resolved['path']); + } + return $resolved; + } +} + +/** + * Ensure a file exists on local disk for job processors. + * Local first (legacy leftovers); if missing and S3 has it, download into + * writable/cache/storage_runtime/ (temp) and auto-clean at request shutdown. + */ +if (! function_exists('storage_ensure_local_file')) { + function storage_ensure_local_file(string $uploadPath, string $fileName): ?string + { + $fileName = basename(trim($fileName)); + if ($fileName === '') { + return null; + } + + $storage = \Config\Services::getFileStorageService(); + $localPath = $storage->resolveLocalPath($uploadPath, $fileName); + if (is_file($localPath) && is_readable($localPath)) { + return $localPath; + } + + if (! $storage->usesS3()) { + return null; + } + + $result = $storage->download($uploadPath, $fileName); + if (! ($result['success'] ?? false)) { + return null; + } + + $content = $result['content'] ?? null; + if (! is_string($content) || $content === '') { + // download() may have returned a local path already + if (! empty($result['path']) && is_file((string) $result['path'])) { + $existing = (string) $result['path']; + // If path is already under runtime, register cleanup; else leave as-is. + storage_register_temp_file($existing); + return $existing; + } + return null; + } + + $tmpDir = storage_runtime_temp_dir(); + if (! is_dir($tmpDir)) { + @mkdir($tmpDir, 0755, true); + } + + $tmpPath = $tmpDir . DIRECTORY_SEPARATOR . uniqid('stor_', true) . '_' . $fileName; + if (@file_put_contents($tmpPath, $content) === false || ! is_file($tmpPath)) { + return null; + } + + storage_register_temp_file($tmpPath); + + return $tmpPath; + } +} + +/** + * After generating a local workbook, mirror to S3 (when enabled) and remove local permanent copy. + */ +if (! function_exists('storage_mirror_generated_file')) { + function storage_mirror_generated_file(string $localPath, string $uploadPath, string $fileName): bool + { + if (! is_file($localPath)) { + return false; + } + + $storage = \Config\Services::getFileStorageService(); + if (! $storage->usesS3()) { + return true; + } + + $result = $storage->upload($localPath, rtrim($uploadPath, '/\\'), $fileName); + @unlink($localPath); + + return (bool) ($result['success'] ?? false); + } +} + if (! function_exists('file_unlink')) { function file_unlink($filepath) { @@ -580,8 +1238,11 @@ if (! function_exists('excelFileGDriveUpload')) { ->first(); if ($data) { - $uploadFilePath .= '/' . $data['file_name']; - // dd($data, $uploadFilePath); + $resolved = storage_ensure_local_file($uploadFilePath, $data['file_name']); + if (empty($resolved) || ! is_file($resolved)) { + return; + } + $uploadFilePath = $resolved; $GoogleDriveController = new GoogleDriveController(); $result = $GoogleDriveController->uploadFiletoGdrive( diff --git a/app/Libraries/FileStorageService.php b/app/Libraries/FileStorageService.php new file mode 100644 index 00000000..dc89073e --- /dev/null +++ b/app/Libraries/FileStorageService.php @@ -0,0 +1,342 @@ +s3Service = \Config\Services::getS3Service(false); + $this->bucket = getenv('AWS_FILE_UPLOAD_BUCKET') ?: getenv('AWS_BUCKET'); + $this->driver = strtolower((string) (getenv('FILE_STORAGE_DRIVER') ?: 's3')); + } + + public function usesS3(): bool + { + return $this->driver === 's3' && !empty($this->bucket); + } + + public function getBucket(): ?string + { + return $this->bucket ?: null; + } + + /** + * Convert a legacy local upload path to an S3 folder prefix. + * Last path segment becomes the bucket folder name. + * e.g. WRITEPATH/uploads/client_kyc_documents -> client_kyc_documents + */ + public function normalizeFolder(string $filepath): string + { + $path = str_replace('\\', '/', trim($filepath)); + $path = rtrim($path, '/'); + + if ($path === '') { + return ''; + } + + $segments = array_values(array_filter(explode('/', $path), static function ($segment) { + return $segment !== ''; + })); + + return $segments ? (string) end($segments) : ''; + } + + public function buildKey(string $folder, string $fileName): string + { + $folder = trim($folder, '/'); + $fileName = ltrim($fileName, '/'); + + return $folder !== '' ? $folder . '/' . $fileName : $fileName; + } + + public function resolveLocalPath(string $filepath, string $fileName): string + { + return rtrim($filepath, '/\\') . DIRECTORY_SEPARATOR . $fileName; + } + + /** + * Collect upload file metadata for logging. + */ + protected function getFileMeta($file, string $fallbackName): array + { + $isUploadedObject = is_object($file) && method_exists($file, 'isValid'); + $resolvedName = $fallbackName; + $tmpPath = null; + $size = null; + $mimeType = null; + $extension = null; + $isValid = null; + $hasMoved = null; + $error = null; + + if ($isUploadedObject) { + if (method_exists($file, 'getClientName')) { + $resolvedName = $file->getClientName() ?: $fallbackName; + } + $tmpPath = method_exists($file, 'getTempName') ? $file->getTempName() : null; + $size = method_exists($file, 'getSize') ? $file->getSize() : null; + $mimeType = method_exists($file, 'getMimeType') ? $file->getMimeType() : null; + $extension = method_exists($file, 'getExtension') ? $file->getExtension() : null; + $isValid = method_exists($file, 'isValid') ? $file->isValid() : null; + $hasMoved = method_exists($file, 'hasMoved') ? $file->hasMoved() : null; + $error = method_exists($file, 'getErrorString') ? $file->getErrorString() : null; + } elseif (is_string($file)) { + $tmpPath = $file; + $resolvedName = basename($file) ?: $fallbackName; + if (is_file($file)) { + $size = filesize($file); + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $mimeType = $finfo ? finfo_file($finfo, $file) : null; + if ($finfo) { + finfo_close($finfo); + } + } + $extension = pathinfo($resolvedName, PATHINFO_EXTENSION); + } + + return [ + 'resolved_name' => $resolvedName, + 'tmp_path' => $tmpPath, + 'size_bytes' => $size, + 'mime_type' => $mimeType, + 'extension' => $extension, + 'is_valid' => $isValid, + 'has_moved' => $hasMoved, + 'upload_error' => $error, + ]; + } + + protected function logUpload(string $event, array $details, string $level = 'info'): void + { + log_message($level, 'FileStorage Upload ' . $event . ' | ' . json_encode($details, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + /** + * Upload a file using folder path + file name. + * + * @return array{success:bool,key:?string,file_name:string,message:string,url:?string} + */ + public function upload($file, string $filepath, string $fileName): array + { + $folder = $this->normalizeFolder($filepath); + $key = $this->buildKey($folder, $fileName); + $fileMeta = $this->getFileMeta($file, $fileName); + + $baseLog = [ + 'driver' => $this->driver, + 'uses_s3' => $this->usesS3(), + 'bucket' => $this->bucket, + 'filepath' => $filepath, + 'folder' => $folder, + 'key' => $key, + 'file_name' => $fileName, + 'file' => $fileMeta, + ]; + + $this->logUpload('started', $baseLog); + + if ($this->usesS3()) { + try { + $result = $this->s3Service->upload($file, $folder, $fileName, $this->bucket); + $response = [ + 'success' => (bool) ($result['success'] ?? false), + 'key' => $result['key'] ?? $key, + 'file_name' => $fileName, + 'url' => $result['url'] ?? null, + 'message' => $result['message'] ?? '', + ]; + + if ($response['success']) { + $this->logUpload('s3_success', array_merge($baseLog, [ + 'result' => $response, + 's3_result' => $result, + ])); + } else { + $this->logUpload('s3_failure', array_merge($baseLog, [ + 'result' => $response, + 's3_result' => $result, + ]), 'error'); + } + + return $response; + } catch (\Throwable $e) { + $response = [ + 'success' => false, + 'key' => null, + 'file_name' => $fileName, + 'url' => null, + 'message' => 'Upload exception: ' . $e->getMessage(), + ]; + + $this->logUpload('s3_exception', array_merge($baseLog, [ + 'result' => $response, + 'error_message' => $e->getMessage(), + 'error_code' => $e->getCode(), + 'exception_class' => get_class($e), + 'error_file' => $e->getFile(), + 'error_line' => $e->getLine(), + ]), 'error'); + + return $response; + } + } + + if (is_object($file) && method_exists($file, 'isValid') && $file->isValid() && !$file->hasMoved()) { + try { + $targetDir = rtrim($filepath, '/\\'); + if (!is_dir($targetDir)) { + mkdir($targetDir, 0755, true); + } + $file->move($targetDir . DIRECTORY_SEPARATOR, $fileName); + + $response = [ + 'success' => true, + 'key' => $key, + 'file_name' => $fileName, + 'url' => null, + 'message' => 'File uploaded successfully', + 'local_path' => $this->resolveLocalPath($filepath, $fileName), + ]; + + $this->logUpload('local_success', array_merge($baseLog, [ + 'result' => $response, + ])); + + return $response; + } catch (\Throwable $e) { + $response = [ + 'success' => false, + 'key' => null, + 'file_name' => $fileName, + 'url' => null, + 'message' => 'Local upload failed: ' . $e->getMessage(), + ]; + + $this->logUpload('local_failure', array_merge($baseLog, [ + 'result' => $response, + 'error_message' => $e->getMessage(), + 'exception_class' => get_class($e), + 'error_file' => $e->getFile(), + 'error_line' => $e->getLine(), + ]), 'error'); + + return $response; + } + } + + $response = [ + 'success' => false, + 'key' => null, + 'file_name' => $fileName, + 'url' => null, + 'message' => 'Invalid file upload', + ]; + + $this->logUpload('validation_failed', array_merge($baseLog, [ + 'result' => $response, + ]), 'error'); + + return $response; + } + + public function exists(string $filepath, string $fileName): bool + { + $folder = $this->normalizeFolder($filepath); + $key = $this->buildKey($folder, $fileName); + + if ($this->usesS3()) { + return $this->s3Service->exists($key, $this->bucket); + } + + return is_file($this->resolveLocalPath($filepath, $fileName)); + } + + public function getPresignedUrl(string $filepath, string $fileName, int $expiration = 60): array + { + $folder = $this->normalizeFolder($filepath); + $key = $this->buildKey($folder, $fileName); + + if (!$this->usesS3()) { + return [ + 'success' => false, + 'url' => null, + 'message' => 'Presigned URLs are only available when S3 storage is enabled', + ]; + } + + return $this->s3Service->getPresignedUrl($key, $expiration, $this->bucket); + } + + /** + * Download file contents for forced browser download (attachment). + * + * @return array{success:bool,content:?string,path:?string,message:string} + */ + public function download(string $filepath, string $fileName): array + { + $localPath = $this->resolveLocalPath($filepath, $fileName); + + if (is_file($localPath)) { + return [ + 'success' => true, + 'content' => null, + 'path' => $localPath, + 'message' => 'Local file found', + ]; + } + + if (!$this->usesS3()) { + return [ + 'success' => false, + 'content' => null, + 'path' => null, + 'message' => 'File not found', + ]; + } + + $folder = $this->normalizeFolder($filepath); + $key = $this->buildKey($folder, $fileName); + $result = $this->s3Service->download($key, null, $this->bucket); + + return [ + 'success' => (bool) ($result['success'] ?? false), + 'content' => $result['content'] ?? null, + 'path' => null, + 'message' => $result['message'] ?? 'Download failed', + 'key' => $key, + ]; + } + + public function delete(string $filepath, string $fileName): array + { + $folder = $this->normalizeFolder($filepath); + $key = $this->buildKey($folder, $fileName); + $localPath = $this->resolveLocalPath($filepath, $fileName); + + // Always remove leftover local copy when present (S3-only cleanup). + if (is_file($localPath)) { + @unlink($localPath); + } + + if ($this->usesS3()) { + return $this->s3Service->delete($key, $this->bucket); + } + + return [ + 'success' => true, + 'message' => 'File deleted successfully', + ]; + } +} diff --git a/app/Libraries/S3Service.php b/app/Libraries/S3Service.php index 872b13ab..391a5f59 100644 --- a/app/Libraries/S3Service.php +++ b/app/Libraries/S3Service.php @@ -4,7 +4,6 @@ namespace App\Libraries; use Aws\S3\S3Client; use Aws\Exception\AwsException; -use CodeIgniter\HTTP\ResponseInterface; class S3Service { @@ -17,7 +16,7 @@ class S3Service { $this->bucket = getenv('AWS_BUCKET'); $this->region = getenv('AWS_DEFAULT_REGION'); - + $this->s3Client = new S3Client([ 'version' => 'latest', 'region' => $this->region, @@ -27,44 +26,71 @@ class S3Service ], ]); - $this->baseUrl = "https://{$this->bucket}.s3.{$this->region}.amazonaws.com/"; - // echo $this->baseUrl;die(); + $this->baseUrl = $this->buildBaseUrl($this->bucket); + } + + protected function resolveBucket(?string $bucket = null): string + { + return $bucket ?: $this->bucket; + } + + protected function buildBaseUrl(string $bucket): string + { + return "https://{$bucket}.s3.{$this->region}.amazonaws.com/"; } /** * Upload file to S3 - * + * * @param mixed $file File object or file path * @param string $folder Folder path in S3 bucket (optional) * @param string $fileName Custom file name (optional) + * @param string|null $bucket Override destination bucket (optional) * @return array ['success' => bool, 'url' => string, 'key' => string, 'message' => string] */ - public function upload($file, string $folder = '', string $fileName = null): array + public function upload($file, string $folder = '', string $fileName = null, ?string $bucket = null): array { + $targetBucket = $this->resolveBucket($bucket); + $startLog = [ + 'bucket' => $targetBucket, + 'region' => $this->region, + 'folder' => $folder, + 'file_name' => $fileName, + 'file_type' => is_object($file) ? get_class($file) : gettype($file), + ]; + log_message('info', 'S3 Upload started | ' . json_encode($startLog, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + try { // Handle CodeIgniter file upload object if (is_object($file) && method_exists($file, 'isValid')) { if (!$file->isValid()) { - return [ + $fail = [ 'success' => false, 'message' => 'Invalid file upload', 'url' => null, 'key' => null ]; + log_message('error', 'S3 Upload validation failed | ' . json_encode(array_merge($startLog, [ + 'result' => $fail, + 'error' => method_exists($file, 'getErrorString') ? $file->getErrorString() : null, + ]), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + return $fail; } $filePath = $file->getTempName(); $originalName = $fileName ?? $file->getClientName(); + $sizeBytes = method_exists($file, 'getSize') ? $file->getSize() : null; } else { // Handle file path string $filePath = $file; $originalName = $fileName ?? basename($file); + $sizeBytes = is_string($filePath) && is_file($filePath) ? filesize($filePath) : null; } // Generate unique file name $extension = pathinfo($originalName, PATHINFO_EXTENSION); // $uniqueName = pathinfo($originalName, PATHINFO_FILENAME) . '_' . time() . '.' . $extension; $uniqueName = pathinfo($originalName, PATHINFO_FILENAME) . '.' . $extension; - + // Build S3 key (path) $key = $folder ? rtrim($folder, '/') . '/' . $uniqueName : $uniqueName; @@ -75,22 +101,55 @@ class S3Service finfo_close($finfo); $result = $this->s3Client->putObject([ - 'Bucket' => $this->bucket, + 'Bucket' => $targetBucket, 'Key' => $key, 'Body' => fopen($filePath, 'rb'), // Stream instead of loading into memory 'ContentType' => $mimeType, // 'ACL' => 'public-read', ]); - return [ + $success = [ 'success' => true, - 'url' => $this->baseUrl . $key, + 'url' => $this->buildBaseUrl($targetBucket) . $key, 'key' => $key, 'message' => 'File uploaded successfully' ]; + log_message('info', 'S3 Upload successful | ' . json_encode(array_merge($startLog, [ + 'tmp_path' => $filePath, + 'original_name' => $originalName, + 'unique_name' => $uniqueName, + 'key' => $key, + 'mime_type' => $mimeType, + 'size_bytes' => $sizeBytes, + 'etag' => $result['ETag'] ?? null, + 'object_url' => $result['ObjectURL'] ?? null, + 'result' => $success, + ]), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + + return $success; + } catch (AwsException $e) { - log_message('error', 'S3 Upload Error: ' . $e->getMessage()); + log_message('error', 'S3 Upload Error | ' . json_encode(array_merge($startLog, [ + 'error_message' => $e->getMessage(), + 'aws_error_code' => $e->getAwsErrorCode(), + 'aws_error_type' => $e->getAwsErrorType(), + 'status_code' => $e->getStatusCode(), + 'request_id' => $e->getAwsRequestId(), + ]), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + return [ + 'success' => false, + 'message' => 'Upload failed: ' . $e->getMessage(), + 'url' => null, + 'key' => null + ]; + } catch (\Throwable $e) { + log_message('error', 'S3 Upload Exception | ' . json_encode(array_merge($startLog, [ + 'error_message' => $e->getMessage(), + 'exception_class' => get_class($e), + 'error_file' => $e->getFile(), + 'error_line' => $e->getLine(), + ]), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); return [ 'success' => false, 'message' => 'Upload failed: ' . $e->getMessage(), @@ -102,16 +161,17 @@ class S3Service /** * Download file from S3 - * + * * @param string $key S3 object key (file path in bucket) * @param string $savePath Local path to save the file (optional) + * @param string|null $bucket Override source bucket (optional) * @return array ['success' => bool, 'path' => string, 'content' => string, 'message' => string] */ - public function download(string $key, string $savePath = null): array + public function download(string $key, string $savePath = null, ?string $bucket = null): array { try { $result = $this->s3Client->getObject([ - 'Bucket' => $this->bucket, + 'Bucket' => $this->resolveBucket($bucket), 'Key' => $key, ]); @@ -124,7 +184,7 @@ class S3Service mkdir($directory, 0755, true); } file_put_contents($savePath, $content); - + return [ 'success' => true, 'path' => $savePath, @@ -153,16 +213,17 @@ class S3Service /** * Get a pre-signed URL for temporary access to a private file - * + * * @param string $key S3 object key * @param int $expiration Expiration time in minutes (default: 60) + * @param string|null $bucket Override source bucket (optional) * @return array ['success' => bool, 'url' => string, 'message' => string] */ - public function getPresignedUrl(string $key, int $expiration = 60): array + public function getPresignedUrl(string $key, int $expiration = 60, ?string $bucket = null): array { try { $cmd = $this->s3Client->getCommand('GetObject', [ - 'Bucket' => $this->bucket, + 'Bucket' => $this->resolveBucket($bucket), 'Key' => $key ]); @@ -187,15 +248,16 @@ class S3Service /** * Delete file from S3 - * + * * @param string $key S3 object key + * @param string|null $bucket Override source bucket (optional) * @return array ['success' => bool, 'message' => string] */ - public function delete(string $key): array + public function delete(string $key, ?string $bucket = null): array { try { $this->s3Client->deleteObject([ - 'Bucket' => $this->bucket, + 'Bucket' => $this->resolveBucket($bucket), 'Key' => $key, ]); @@ -215,45 +277,61 @@ class S3Service /** * Check if file exists in S3 - * + * * @param string $key S3 object key + * @param string|null $bucket Override source bucket (optional) * @return bool */ - public function exists(string $key): bool + public function exists(string $key, ?string $bucket = null): bool { - return $this->s3Client->doesObjectExist($this->bucket, $key); + return $this->s3Client->doesObjectExist($this->resolveBucket($bucket), $key); } /** * List files in S3 bucket - * + * * @param string $prefix Folder prefix (optional) + * @param string|null $bucket Override source bucket (optional) * @return array ['success' => bool, 'files' => array, 'message' => string] */ - public function listFiles(string $prefix = ''): array + public function listFiles(string $prefix = '', ?string $bucket = null): array { try { - $result = $this->s3Client->listObjectsV2([ - 'Bucket' => $this->bucket, - 'Prefix' => $prefix, - ]); - + $targetBucket = $this->resolveBucket($bucket); $files = []; - if (isset($result['Contents'])) { - foreach ($result['Contents'] as $object) { - $files[] = [ - 'key' => $object['Key'], - 'size' => $object['Size'], - 'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'), - 'url' => $this->baseUrl . $object['Key'] - ]; + $token = null; + + do { + $params = [ + 'Bucket' => $targetBucket, + 'Prefix' => $prefix, + ]; + if ($token !== null) { + $params['ContinuationToken'] = $token; } - } + + $result = $this->s3Client->listObjectsV2($params); + + if (isset($result['Contents'])) { + foreach ($result['Contents'] as $object) { + $files[] = [ + 'key' => $object['Key'], + 'size' => $object['Size'], + 'last_modified' => $object['LastModified']->format('Y-m-d H:i:s'), + 'url' => $this->buildBaseUrl($targetBucket) . $object['Key'], + ]; + } + } + + $token = ! empty($result['IsTruncated']) + ? ($result['NextContinuationToken'] ?? null) + : null; + } while ($token !== null); return [ 'success' => true, 'files' => $files, - 'message' => 'Files retrieved successfully' + 'message' => 'Files retrieved successfully', ]; } catch (AwsException $e) { @@ -261,8 +339,8 @@ class S3Service return [ 'success' => false, 'files' => [], - 'message' => 'Failed to list files: ' . $e->getMessage() + 'message' => 'Failed to list files: ' . $e->getMessage(), ]; } } -} \ No newline at end of file +} diff --git a/app/Libraries/ZipService.php b/app/Libraries/ZipService.php index 42030ffc..498f1b0b 100644 --- a/app/Libraries/ZipService.php +++ b/app/Libraries/ZipService.php @@ -46,7 +46,7 @@ class ZipService unlink($tempZipPath); } - $get_presinged_url = $this->s3Service->getPresignedUrl($uploadResult['key'], 2,880); + $get_presinged_url = $this->s3Service->getPresignedUrl($uploadResult['key'], 2880); // To delete the original temprory folder after zipping and uploading // $this->deleteDirectory($localFolderPath); diff --git a/app/Models/ClientKYCDocsModel.php b/app/Models/ClientKYCDocsModel.php index 913fb375..0107610f 100755 --- a/app/Models/ClientKYCDocsModel.php +++ b/app/Models/ClientKYCDocsModel.php @@ -30,7 +30,7 @@ class ClientKYCDocsModel extends Model public function getClientKYCDriveFilesIndex($client_id,$client_doc_name = '') { - $query = $this->select('client_kyc_documents.client_id,client_kyc_documents.file_name, kyc_docs.file_name as doc_name,"url","client_policy_id","kyc" as file_type') + $query = $this->select('client_kyc_documents.id,client_kyc_documents.client_id,client_kyc_documents.file_name, kyc_docs.file_name as doc_name,"url","client_policy_id","kyc" as file_type') ->join('kyc_docs', 'kyc_docs.id = client_kyc_documents.kyc_doc_type_id') ->where(['client_kyc_documents.client_id' => $client_id, 'client_kyc_documents.is_active' => 1]) ->when($client_doc_name, function($query) use ($client_doc_name){ diff --git a/app/Views/claim_files_upload.php b/app/Views/claim_files_upload.php index c444773d..9b7534be 100644 --- a/app/Views/claim_files_upload.php +++ b/app/Views/claim_files_upload.php @@ -627,17 +627,17 @@ const viewHref = escapeClaimFileListAttr(claimFileViewOpenUrl(item, base_url)); const downloadHref = escapeClaimFileListAttr(claimFileDownloadOpenUrl(item, base_url)); const fileNameLinkHref = viewOk ? viewHref : downloadHref; - const fileNameLinkTitle = viewOk ? 'View in new tab' : 'Download'; + const fileNameLinkTitle = viewOk ? 'View file' : 'Download'; html += ` ${index + 1} ${item.doc_name} - ${item.file_type == 1 ? item.url : item.doc_name} + ${item.file_type == 1 ? item.url : item.doc_name} ${tpaLabel} - + ' + item.file_name; + var url = '' + item.id; if(item.kyc_doc_type_id == '0'){ table += ` ${item.other_docs_name} - ${item.file_name} + ${item.file_name ? item.file_name.replace(/^\d+_[a-f0-9]+_/i, '') : ''} - + `; @@ -589,7 +589,7 @@ // console.log(document.getElementById('name_' + item.kyc_doc_type_id)); setTimeout(function() { $('#name_'+item.kyc_doc_type_id).show(); - $('#name_'+item.kyc_doc_type_id).html(item.file_name); + $('#name_'+item.kyc_doc_type_id).html(item.file_name ? item.file_name.replace(/^\d+_[a-f0-9]+_/i, '') : ''); $('#form_'+item.kyc_doc_type_id).hide(); // console.log('step 1') @@ -600,14 +600,14 @@ // "&file_name=" + item.file_name + // "&client_policy_id=client_policy_id"; - var url = '' + item.file_name; + var url = '' + item.id; if(item.file_name != null && item.file_name != ""){ // console.log('step 2') $('#download_'+item.kyc_doc_type_id).show(); - $('#download_' + item.kyc_doc_type_id).attr('href', url).attr('target', '_blank');; + $('#download_' + item.kyc_doc_type_id).attr('href', url).removeAttr('target').attr('download', ''); $('#delete_'+item.kyc_doc_type_id).show(); } else{ diff --git a/app/Views/client_kyc_2.php b/app/Views/client_kyc_2.php index 7a34acc8..20ede196 100755 --- a/app/Views/client_kyc_2.php +++ b/app/Views/client_kyc_2.php @@ -282,11 +282,13 @@ $(document).on('click', '.btn-download-kyc', function (e) { e.preventDefault(); // Prevent default link behavior - // var kyc_id = $(this).attr('data-id'); - // var client_id = $(this).attr('data-client_id'); - var file = $(this).attr('data-file'); - if (file) { - window.location.href = "/" + file; + var kycId = $(this).attr('data-id'); + var file = $(this).attr('data-file'); + if (kycId) { + window.location.href = "/" + kycId; + } else if (file) { + // Legacy fallback for older rows/markup + window.location.href = "/" + encodeURIComponent(file); } else { toastr.warning('File is missing.'); } diff --git a/app/Views/client_kyc_other_table.php b/app/Views/client_kyc_other_table.php index c6629722..15beece5 100644 --- a/app/Views/client_kyc_other_table.php +++ b/app/Views/client_kyc_other_table.php @@ -1,11 +1,12 @@ $item): ?> + - + - + $item): ?> + - +
@@ -16,10 +20,10 @@ - + - - + + $value) : $sno = $index + 1; - $fileName = !empty($value['file_name']) ? $value['file_name'] : '-'; + $storedName = !empty($value['file_name']) ? $value['file_name'] : ''; + $fileName = $storedName !== '' ? storage_upload_display_name($storedName) : '-'; ?> @@ -13,13 +14,14 @@ @@ -36,7 +38,7 @@ - +
diff --git a/app/Views/policy_transaction_endorsement_list.php b/app/Views/policy_transaction_endorsement_list.php index 4a7a2f79..7246570e 100644 --- a/app/Views/policy_transaction_endorsement_list.php +++ b/app/Views/policy_transaction_endorsement_list.php @@ -1109,14 +1109,14 @@ row.append($('').text(index+1)); row.append($('').text(item.doc_name)); - row.append($('').text(item.file_name)); + row.append($('').text(item.file_name ? String(item.file_name).replace(/^\d+_[a-f0-9]+_/i, '') : '')); - var url = '' + item.file_name; + var url = '' + item.id; var link = $('') .attr('href', url) - .attr('target', '_blank') // Open in a new tab + .attr('download', true) .attr('style', 'font-size:18px;') .attr('data-id', item.id) .addClass('mdi mdi-download') diff --git a/app/Views/policy_transaction_inception_list.php b/app/Views/policy_transaction_inception_list.php index add7a740..b0007253 100644 --- a/app/Views/policy_transaction_inception_list.php +++ b/app/Views/policy_transaction_inception_list.php @@ -1356,7 +1356,7 @@ function appendFileTableBody(data) row.append($('').text(index+1)); row.append($('').text(item.doc_name)); - row.append($('').text(item.file_name)); + row.append($('').text(item.file_name ? String(item.file_name).replace(/^\d+_[a-f0-9]+_/i, '') : '')); // var url = "" + // "?client_id=" + item.client_id + @@ -1364,12 +1364,12 @@ function appendFileTableBody(data) // "&file_name=" + item.file_name + // "&client_policy_id=" + item.client_policy_id; - var url = '' + item.file_name; + var url = '' + item.id; var link = $('') .attr('href', url) - .attr('target', '_blank') // Open in a new tab + .attr('download', true) .attr('style', 'font-size:18px;') .attr('data-id', item.id) .addClass('mdi mdi-download') @@ -1467,11 +1467,12 @@ function appendVehicleFileTableBody(data) // Add row number, document name, and file name row.append($('').text(index + 1)); row.append($('').text(item.other_docs_name)); - row.append($('').text(item.file_name)); + row.append($('').text(item.file_name ? String(item.file_name).replace(/^\d+_[a-f0-9]+_/i, '') : '')); // Create the download link var downloadLink = $('') - .attr('href', '' + item.file_name) + .attr('href', '' + item.id) + .attr('download', true) .attr('style', 'font-size:18px;') .attr('data-id', item.id) .addClass('mdi mdi-download') diff --git a/app/Views/policy_transaction_inception_list_2.php b/app/Views/policy_transaction_inception_list_2.php index 3b3d9912..f74aac7e 100644 --- a/app/Views/policy_transaction_inception_list_2.php +++ b/app/Views/policy_transaction_inception_list_2.php @@ -1120,7 +1120,7 @@ function appendFileTableBody(data) row.append($('').text(index+1)); row.append($('').text(item.doc_name)); - row.append($('').text(item.file_name)); + row.append($('').text(item.file_name ? String(item.file_name).replace(/^\d+_[a-f0-9]+_/i, '') : '')); // var url = "" + // "?client_id=" + item.client_id + @@ -1128,12 +1128,12 @@ function appendFileTableBody(data) // "&file_name=" + item.file_name + // "&client_policy_id=" + item.client_policy_id; - var url = '' + item.file_name; + var url = '' + item.id; var link = $('') .attr('href', url) - .attr('target', '_blank') // Open in a new tab + .attr('download', true) .attr('style', 'font-size:18px;') .attr('data-id', item.id) .addClass('mdi mdi-download') @@ -1224,11 +1224,12 @@ function appendVehicleFileTableBody(data) // Add row number, document name, and file name row.append($('').text(index + 1)); row.append($('').text(item.other_docs_name)); - row.append($('').text(item.file_name)); + row.append($('').text(item.file_name ? String(item.file_name).replace(/^\d+_[a-f0-9]+_/i, '') : '')); // Create the download link var downloadLink = $('') - .attr('href', '' + item.file_name) + .attr('href', '' + item.id) + .attr('download', true) .attr('style', 'font-size:18px;') .attr('data-id', item.id) .addClass('mdi mdi-download') diff --git a/app/Views/ticket_conversation.php b/app/Views/ticket_conversation.php index 9aa64072..078d911f 100644 --- a/app/Views/ticket_conversation.php +++ b/app/Views/ticket_conversation.php @@ -35,7 +35,7 @@ foreach ($message['files_data'] as $index => $datas) { $index = $index + 1; ?> -

File :

+

File :

diff --git a/writable/cache/claim_files_runtime/.gitkeep b/writable/cache/claim_files_runtime/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/writable/cache/storage_runtime/.gitkeep b/writable/cache/storage_runtime/.gitkeep new file mode 100644 index 00000000..e69de29b