Initial profile site commit
This commit is contained in:
+229
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
require_once 'config.php';
|
||||
require_once __DIR__ . '/error_config.php';
|
||||
session_start();
|
||||
set_json_headers();
|
||||
require_auth();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
json_response(['error' => 'POST만 허용됩니다'], 405);
|
||||
}
|
||||
|
||||
if (!isset($_FILES['file']) && !isset($_FILES['image'])) {
|
||||
json_response(['error' => '파일 업로드 실패'], 400);
|
||||
}
|
||||
|
||||
// 'file' 또는 'image' 둘 다 받기 (호환성)
|
||||
$file = $_FILES['file'] ?? $_FILES['image'];
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
json_response(['error' => '파일 업로드 실패 (코드: ' . $file['error'] . ')'], 400);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 파일 사이즈 조정
|
||||
// 이미지 5MB / 동영상 100MB / 일반 파일 50MB
|
||||
// 단위: byte (1MB = 1024 * 1024)
|
||||
// 제한 없음으로 하려면 PHP_INT_MAX로 설정
|
||||
// 단, Synology PHP의 upload_max_filesize / post_max_size 설정도 함께 조정 필요
|
||||
// =====================================================
|
||||
$MAX_IMAGE_SIZE = 5 * 1024 * 1024; // 이미지: 5 MB
|
||||
$MAX_VIDEO_SIZE = 100 * 1024 * 1024; // 동영상: 100 MB
|
||||
$MAX_FILE_SIZE = 50 * 1024 * 1024; // 일반 파일: 50 MB
|
||||
// =====================================================
|
||||
|
||||
// 허용 MIME 타입과 확장자
|
||||
$image_types = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp'
|
||||
];
|
||||
$video_types = [
|
||||
'video/mp4' => 'mp4',
|
||||
'video/webm' => 'webm',
|
||||
'video/ogg' => 'ogv',
|
||||
'video/quicktime' => 'mov'
|
||||
];
|
||||
// 일반 파일 허용 목록 (MIME → 확장자)
|
||||
$file_types = [
|
||||
'application/pdf' => 'pdf',
|
||||
'application/zip' => 'zip',
|
||||
'application/x-zip-compressed' => 'zip',
|
||||
'application/x-7z-compressed' => '7z',
|
||||
'application/x-rar-compressed' => 'rar',
|
||||
'application/vnd.ms-excel' => 'xls',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
|
||||
'application/msword' => 'doc',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
|
||||
'application/vnd.ms-powerpoint' => 'ppt',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
|
||||
'text/plain' => 'txt',
|
||||
'text/markdown' => 'md',
|
||||
'text/csv' => 'csv',
|
||||
'application/json' => 'json',
|
||||
];
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mime = $finfo->file($file['tmp_name']);
|
||||
|
||||
$is_image = isset($image_types[$mime]);
|
||||
$is_video = isset($video_types[$mime]);
|
||||
$is_file = !$is_image && !$is_video && isset($file_types[$mime]);
|
||||
|
||||
if (!$is_image && !$is_video && !$is_file) {
|
||||
json_response(['error' => '허용되지 않는 파일 형식입니다. (이미지/동영상/PDF/ZIP/문서 등)'], 400);
|
||||
}
|
||||
|
||||
// 파일 크기 검증
|
||||
if ($is_image && $file['size'] > $MAX_IMAGE_SIZE) {
|
||||
$mb = round($MAX_IMAGE_SIZE / 1024 / 1024, 1);
|
||||
json_response(['error' => "이미지 파일은 {$mb}MB 이하여야 합니다"], 400);
|
||||
}
|
||||
if ($is_video && $file['size'] > $MAX_VIDEO_SIZE) {
|
||||
$mb = round($MAX_VIDEO_SIZE / 1024 / 1024, 1);
|
||||
json_response(['error' => "동영상 파일은 {$mb}MB 이하여야 합니다"], 400);
|
||||
}
|
||||
if ($is_file && $file['size'] > $MAX_FILE_SIZE) {
|
||||
$mb = round($MAX_FILE_SIZE / 1024 / 1024, 1);
|
||||
json_response(['error' => "파일은 {$mb}MB 이하여야 합니다"], 400);
|
||||
}
|
||||
|
||||
if ($is_image) {
|
||||
$ext = $image_types[$mime];
|
||||
$kind = 'image';
|
||||
$prefix = 'img_';
|
||||
} elseif ($is_video) {
|
||||
$ext = $video_types[$mime];
|
||||
$kind = 'video';
|
||||
$prefix = 'vid_';
|
||||
} else {
|
||||
$ext = $file_types[$mime];
|
||||
$kind = 'file';
|
||||
$prefix = 'file_';
|
||||
// 일반 파일은 원본 파일명 일부를 살림 (정리 후)
|
||||
$origName = pathinfo($file['name'], PATHINFO_FILENAME);
|
||||
$origName = preg_replace('/[^\p{L}\p{N}\-_]/u', '_', $origName);
|
||||
$origName = mb_substr($origName, 0, 30, 'UTF-8');
|
||||
$prefix = $origName . '_';
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 폴더명 생성
|
||||
// =====================================================
|
||||
function sanitize_folder_name($title) {
|
||||
$title = trim($title);
|
||||
if ($title === '') return '';
|
||||
|
||||
// 위험한 문자 제거
|
||||
$title = preg_replace('/[\/\\\\\:\*\?"<>\|]/u', '', $title);
|
||||
$title = preg_replace('/\.\.+/u', '', $title);
|
||||
|
||||
// 공백을 하이픈으로
|
||||
$title = preg_replace('/\s+/u', '-', $title);
|
||||
|
||||
// 영문/숫자/한글/하이픈/언더스코어 외 모두 제거
|
||||
$title = preg_replace('/[^\p{L}\p{N}\-_]/u', '', $title);
|
||||
|
||||
$title = trim($title, '-_');
|
||||
|
||||
if (mb_strlen($title, 'UTF-8') > 50) {
|
||||
$title = mb_substr($title, 0, 50, 'UTF-8');
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
$projectTitle = isset($_POST['project_title']) ? trim($_POST['project_title']) : '';
|
||||
$folderName = sanitize_folder_name($projectTitle);
|
||||
|
||||
if ($folderName === '') {
|
||||
$folderName = '_untitled';
|
||||
}
|
||||
|
||||
$uploadDir = UPLOADS_DIR . '/' . $folderName;
|
||||
|
||||
if (!is_dir(UPLOADS_DIR)) {
|
||||
@mkdir(UPLOADS_DIR, 0775, true);
|
||||
}
|
||||
if (!is_dir($uploadDir)) {
|
||||
if (!@mkdir($uploadDir, 0775, true)) {
|
||||
json_response(['error' => '폴더 생성 실패. uploads 폴더에 쓰기 권한을 확인해주세요.'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 안전한 파일명 생성
|
||||
$filename = uniqid($prefix, true) . '.' . $ext;
|
||||
$target_path = $uploadDir . '/' . $filename;
|
||||
|
||||
// =====================================================
|
||||
// 이미지: WebP 변환 시도 (GD 지원 시)
|
||||
// 동영상/일반 파일은 원본 그대로 저장
|
||||
// =====================================================
|
||||
function try_save_as_webp(string $tmp, string $dest_dir, string $base_name, string $mime, int $quality = 82): string|false {
|
||||
if (!function_exists('imagewebp')) return false;
|
||||
|
||||
$src = match ($mime) {
|
||||
'image/jpeg' => @imagecreatefromjpeg($tmp),
|
||||
'image/png' => @imagecreatefrompng($tmp),
|
||||
'image/gif' => @imagecreatefromgif($tmp),
|
||||
'image/webp' => @imagecreatefromwebp($tmp),
|
||||
default => false,
|
||||
};
|
||||
if (!$src) return false;
|
||||
|
||||
// PNG 투명도 보존
|
||||
if ($mime === 'image/png') {
|
||||
imagepalettetotruecolor($src);
|
||||
imagealphablending($src, true);
|
||||
imagesavealpha($src, true);
|
||||
}
|
||||
|
||||
// 가로 1200px 초과 시 리사이즈
|
||||
$ow = imagesx($src);
|
||||
$oh = imagesy($src);
|
||||
if ($ow > 1200) {
|
||||
$nw = 1200;
|
||||
$nh = (int)round($oh * (1200 / $ow));
|
||||
$dst = imagecreatetruecolor($nw, $nh);
|
||||
imagealphablending($dst, false);
|
||||
imagesavealpha($dst, true);
|
||||
$t = imagecolorallocatealpha($dst, 0, 0, 0, 127);
|
||||
imagefilledrectangle($dst, 0, 0, $nw, $nh, $t);
|
||||
imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $ow, $oh);
|
||||
imagedestroy($src);
|
||||
$src = $dst;
|
||||
}
|
||||
|
||||
$dest_path = $dest_dir . '/' . $base_name . '.webp';
|
||||
$ok = imagewebp($src, $dest_path, $quality);
|
||||
imagedestroy($src);
|
||||
return $ok ? $dest_path : false;
|
||||
}
|
||||
|
||||
if ($is_image) {
|
||||
$base_name = pathinfo($filename, PATHINFO_FILENAME);
|
||||
$webp_path = try_save_as_webp($file['tmp_name'], $uploadDir, $base_name, $mime);
|
||||
if ($webp_path !== false) {
|
||||
$filename = $base_name . '.webp';
|
||||
$target_path = $webp_path;
|
||||
$saved = true;
|
||||
} else {
|
||||
$saved = move_uploaded_file($file['tmp_name'], $target_path);
|
||||
}
|
||||
} else {
|
||||
$saved = move_uploaded_file($file['tmp_name'], $target_path);
|
||||
}
|
||||
|
||||
if ($saved) {
|
||||
json_response([
|
||||
'success' => true,
|
||||
'url' => 'uploads/' . $folderName . '/' . $filename,
|
||||
'filename' => $filename,
|
||||
'original_name' => $file['name'],
|
||||
'folder' => $folderName,
|
||||
'kind' => $kind
|
||||
]);
|
||||
} else {
|
||||
$err = error_get_last();
|
||||
json_response(['error' => '파일 저장 실패', 'detail' => $err, 'target' => $target_path, 'tmp' => $file['tmp_name'], 'tmp_exists' => file_exists($file['tmp_name'])], 500);
|
||||
}
|
||||
Reference in New Issue
Block a user