false, 'message' => 'ID required'], 400); } deleteMedia($id); break; default: jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } function getMedia() { global $db; // For a file-based system without a dedicated media DB table, // we can scan the uploads directory. // However, keeping a record in JSON is better for metadata. // Let's assume we have a 'media' table. $media = $db->getAll('media'); // Sync with filesystem? // For simplicity, let's just return DB records. // If empty, one could scan dir, but we'll rely on upload recording. $folder = $_GET['folder'] ?? null; $type = $_GET['type'] ?? null; $search = $_GET['search'] ?? null; if ($folder) { $media = array_filter($media, function($m) use ($folder) { return ($m['folder'] ?? '') === $folder; }); } if ($type) { $media = array_filter($media, function($m) use ($type) { return strpos($m['type'], $type) !== false; }); } if ($search) { $media = array_filter($media, function($m) use ($search) { return stripos($m['name'], $search) !== false; }); } // Sort recent first usort($media, function($a, $b) { return strtotime($b['created_at']) - strtotime($a['created_at']); }); jsonResponse(['success' => true, 'data' => array_values($media)]); } function uploadMedia() { global $db; if (empty($_FILES)) { jsonResponse(['success' => false, 'message' => 'No files uploaded'], 400); } $folder = $_POST['folder'] ?? ''; $uploaded = []; foreach ($_FILES as $key => $file) { // Handle array uploads (multiple files) if (is_array($file['name'])) { foreach ($file['name'] as $idx => $name) { $singleFile = [ 'name' => $name, 'type' => $file['type'][$idx], 'tmp_name' => $file['tmp_name'][$idx], 'error' => $file['error'][$idx], 'size' => $file['size'][$idx] ]; $result = processUpload($singleFile, $folder); if ($result) $uploaded[] = $result; } } else { $result = processUpload($file, $folder); if ($result) $uploaded[] = $result; } } jsonResponse(['success' => true, 'data' => $uploaded]); } function processUpload($file, $folder) { global $db; $res = handleFileUpload($file, $folder); if ($res['success']) { $mediaItem = [ 'name' => $file['name'], 'path' => $res['path'], // Relative path 'full_url' => SITE_URL . '/' . $res['path'], 'type' => $file['type'], 'size' => $file['size'], 'folder' => $folder, 'created_at' => date('Y-m-d H:i:s') ]; return $db->insert('media', $mediaItem); } return null; } function deleteMedia($id) { deleteMediaInternal($id); jsonResponse(['success' => true, 'message' => 'File deleted']); } function deleteMediaInternal($id) { global $db; $item = $db->get('media', $id); if ($item) { // Delete physical file with path traversal protection $filepath = __DIR__ . '/../' . $item['path']; if (file_exists($filepath)) { // Verify path is within uploads directory (prevent directory traversal) $uploadDir = realpath(UPLOAD_DIR); $filePath = realpath($filepath); if ($filePath && $uploadDir && strpos($filePath, $uploadDir) === 0) { if (!unlink($filepath)) { error_log('MSPE: Failed to delete file ' . $filepath); } } else { error_log('MSPE: Path traversal attempt detected in deleteMedia'); } } $db->delete('media', $id); return true; } return false; } function deleteMediaBatch() { $ids = getRequestBody()['ids'] ?? []; $deleted = 0; foreach ($ids as $id) { if (deleteMediaInternal($id)) { $deleted++; } } jsonResponse(['success' => true, 'message' => "{$deleted} file(s) deleted"]); }