152 lines
4.3 KiB
PHP
152 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Libraries;
|
|
|
|
use CodeIgniter\Files\File;
|
|
use CodeIgniter\HTTP\Files\UploadedFile;
|
|
|
|
final class FinancialAttachmentService
|
|
{
|
|
private const MAX_BYTES = 5242880;
|
|
private const ALLOWED_MIMES = [
|
|
'application/pdf',
|
|
'image/jpeg',
|
|
'image/png',
|
|
];
|
|
private const ALLOWED_EXTENSIONS = [
|
|
'jpg',
|
|
'jpeg',
|
|
'pdf',
|
|
'png',
|
|
];
|
|
|
|
public function saveUploadedFile($file, string $subdir): ?string
|
|
{
|
|
$staged = $this->stageUploadedFile($file, $subdir);
|
|
if ($staged === null) {
|
|
return null;
|
|
}
|
|
|
|
$this->finalizeStagedFile($staged);
|
|
|
|
return $staged['final_name'];
|
|
}
|
|
|
|
public function stageUploadedFile($file, string $subdir): ?array
|
|
{
|
|
if (!$file instanceof UploadedFile) {
|
|
return null;
|
|
}
|
|
|
|
if (!$file->isValid() || $file->hasMoved()) {
|
|
return null;
|
|
}
|
|
|
|
$size = (int) ($file->getSize() ?? 0);
|
|
if ($size <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$mime = strtolower((string) $file->getMimeType());
|
|
$ext = strtolower((string) $file->getClientExtension());
|
|
|
|
if (!in_array($mime, self::ALLOWED_MIMES, true) || !in_array($ext, self::ALLOWED_EXTENSIONS, true)) {
|
|
throw new \RuntimeException('Unsupported file type. Use PDF, JPG, or PNG.');
|
|
}
|
|
|
|
if ($size > self::MAX_BYTES) {
|
|
throw new \RuntimeException('File too large. Maximum size is 5 MB.');
|
|
}
|
|
|
|
$name = $file->getRandomName();
|
|
$tmpSubdir = '_tmp' . DIRECTORY_SEPARATOR . trim($subdir, '/');
|
|
$tmpDir = $this->ensureSubdir($tmpSubdir);
|
|
$tmpName = 'pending-' . bin2hex(random_bytes(8)) . '-' . $name;
|
|
$file->move($tmpDir, $tmpName);
|
|
|
|
return [
|
|
'subdir' => trim($subdir, '/'),
|
|
'final_name' => $name,
|
|
'temporary_subdir' => $tmpSubdir,
|
|
'temporary_name' => $tmpName,
|
|
'temporary_path' => $tmpDir . DIRECTORY_SEPARATOR . $tmpName,
|
|
];
|
|
}
|
|
|
|
public function finalizeStagedFile(array $staged): string
|
|
{
|
|
$tmpPath = (string)($staged['temporary_path'] ?? '');
|
|
$finalName = basename((string)($staged['final_name'] ?? ''));
|
|
$subdir = (string)($staged['subdir'] ?? '');
|
|
|
|
if ($tmpPath === '' || $finalName === '' || $subdir === '' || !is_file($tmpPath)) {
|
|
throw new \RuntimeException('Temporary upload is missing.');
|
|
}
|
|
|
|
$finalDir = $this->ensureSubdir($subdir);
|
|
$finalPath = $finalDir . DIRECTORY_SEPARATOR . $finalName;
|
|
if (!@rename($tmpPath, $finalPath)) {
|
|
throw new \RuntimeException('Unable to finalize uploaded evidence.');
|
|
}
|
|
|
|
return $finalName;
|
|
}
|
|
|
|
public function discardStagedFile(?array $staged): void
|
|
{
|
|
if ($staged === null) {
|
|
return;
|
|
}
|
|
|
|
$tmpPath = (string)($staged['temporary_path'] ?? '');
|
|
if ($tmpPath !== '' && is_file($tmpPath)) {
|
|
@unlink($tmpPath);
|
|
}
|
|
}
|
|
|
|
public function resolvePath(string $subdir, string $filename): ?string
|
|
{
|
|
$safeName = basename($filename);
|
|
if ($safeName === '') {
|
|
return null;
|
|
}
|
|
|
|
$path = $this->ensureSubdir($subdir) . DIRECTORY_SEPARATOR . $safeName;
|
|
|
|
return is_file($path) ? $path : null;
|
|
}
|
|
|
|
public function ensureSubdir(string $subdir): string
|
|
{
|
|
$path = WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . trim($subdir, '/');
|
|
if (!is_dir($path) && !mkdir($path, 0775, true) && !is_dir($path)) {
|
|
throw new \RuntimeException('Unable to prepare upload directory.');
|
|
}
|
|
|
|
return $path;
|
|
}
|
|
|
|
public function detectMime(string $path): string
|
|
{
|
|
if (function_exists('finfo_open')) {
|
|
$handle = finfo_open(FILEINFO_MIME_TYPE);
|
|
if ($handle) {
|
|
$mime = finfo_file($handle, $path);
|
|
finfo_close($handle);
|
|
if (is_string($mime) && $mime !== '') {
|
|
return $mime;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (function_exists('mime_content_type')) {
|
|
$mime = mime_content_type($path);
|
|
if (is_string($mime) && $mime !== '') {
|
|
return $mime;
|
|
}
|
|
}
|
|
|
|
return 'application/octet-stream';
|
|
}
|
|
}
|