%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
<?php
/**
* Helper untuk memvalidasi & menyimpan file unggahan dengan aman.
*/
/**
* Memproses satu file unggahan dari $_FILES.
*
* @param array<string, mixed> $file Satu elemen dari $_FILES.
* @param string $subdir Subdirektori di dalam /uploads (mis. 'dokumen').
* @param array<int, string>|null $allowedExt Whitelist ekstensi (default dari config).
*
* @return array{ok:bool, error?:string, filename?:string, original?:string, size?:int, mime?:string}
*/
function handle_upload(array $file, string $subdir, ?array $allowedExt = null): array
{
global $ALLOWED_UPLOAD_EXT;
$allowedExt = $allowedExt ?? $ALLOWED_UPLOAD_EXT;
if (!isset($file['error']) || is_array($file['error'])) {
return ['ok' => false, 'error' => 'Parameter file tidak valid.'];
}
switch ($file['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
return ['ok' => false, 'error' => 'Tidak ada file yang dipilih.'];
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
return ['ok' => false, 'error' => 'Ukuran file melebihi batas yang diizinkan.'];
default:
return ['ok' => false, 'error' => 'Terjadi kesalahan saat mengunggah file.'];
}
if ($file['size'] > MAX_UPLOAD_SIZE) {
return ['ok' => false, 'error' => 'Ukuran file maksimal ' . format_size(MAX_UPLOAD_SIZE) . '.'];
}
$original = $file['name'];
$ext = strtolower(pathinfo($original, PATHINFO_EXTENSION));
if (!in_array($ext, $allowedExt, true)) {
return ['ok' => false, 'error' => 'Tipe file tidak diizinkan. Diperbolehkan: ' . implode(', ', $allowedExt) . '.'];
}
// Verifikasi MIME secara nyata dari isi file.
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
$allowedMime = [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'image/jpeg', 'image/png', 'image/webp', 'application/zip', 'application/x-zip-compressed',
'application/octet-stream',
];
if (!in_array($mime, $allowedMime, true)) {
return ['ok' => false, 'error' => 'Konten file tidak dikenali atau tidak diizinkan.'];
}
$targetDir = UPLOAD_PATH . '/' . trim($subdir, '/');
if (!is_dir($targetDir) && !mkdir($targetDir, 0775, true) && !is_dir($targetDir)) {
return ['ok' => false, 'error' => 'Gagal membuat direktori penyimpanan.'];
}
$newName = date('Ymd_His') . '_' . bin2hex(random_bytes(6)) . '.' . $ext;
$dest = $targetDir . '/' . $newName;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
return ['ok' => false, 'error' => 'Gagal menyimpan file ke server.'];
}
return [
'ok' => true,
'filename' => $newName,
'original' => $original,
'size' => (int) $file['size'],
'mime' => $mime,
];
}
/**
* Hapus file di dalam /uploads secara aman (mencegah path traversal).
*/
function delete_upload(string $subdir, ?string $filename): void
{
if (empty($filename)) {
return;
}
$filename = basename($filename); // cegah traversal
$path = UPLOAD_PATH . '/' . trim($subdir, '/') . '/' . $filename;
if (is_file($path)) {
@unlink($path);
}
}